From 7833e340ffa64991fd09eeb5f83c008cca5d6ddb Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 22 Feb 2024 22:10:54 +0000 Subject: [PATCH 01/77] Filling out template with PR 3720 --- proposals/p3720.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 proposals/p3720.md diff --git a/proposals/p3720.md b/proposals/p3720.md new file mode 100644 index 0000000000000..c3a173144974a --- /dev/null +++ b/proposals/p3720.md @@ -0,0 +1,70 @@ +# Binding operators + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/3720) + + + +## 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 cb32002e0c74f8f4cf3f61271fe005902a42f27d Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 23 Feb 2024 04:42:51 +0000 Subject: [PATCH 02/77] Rough conversion from Google doc --- proposals/p3720.md | 579 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 579 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index c3a173144974a..b3d4dc70754fa 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -17,6 +17,12 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Background](#background) - [Proposal](#proposal) - [Details](#details) + - [Inheritance and other implicit conversions](#inheritance-and-other-implicit-conversions) + - [Data fields](#data-fields) + - [Generic type of a class member](#generic-type-of-a-class-member) + - [C++ pointer to member](#c-pointer-to-member) + - [Member forwarding](#member-forwarding) + - [Future: properties](#future-properties) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) @@ -34,6 +40,11 @@ is impacted by it? ## Background +- [Qualified names and member access](/docs/design/expressions/member_access.md) +- [Proposal #989: Member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) +- [Proposal #2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) +- [Proposal #2875: Functions, function types, and function calls](https://github.com/carbon-language/carbon-lang/pull/2875) + TODO: Is there any background that readers should consider to fully understand this problem and your approach to solving it? @@ -46,6 +57,574 @@ will that in fact solve it? TODO: Fully explain the details of the proposed solution. +To use instance members of a class, we need to go through the additional step of +_binding_. Consider a class `C`: + +```carbon +class C { + fn Value[self: Self]() -> i32 { return self.x + 5; } + fn Mutate[addr self: Self*]() -> i32 { self.x += 1; return self.x; } + fn Static() -> i32 { return 2; } + var x: i32; +} +``` + +Each member of `C` with a distinct name will have a corresponding type (like +`__TypeOf_C_F`) and value of that type (like `__C_F`). There are two more types +for each member function (either static class function or method), though, that +[adapt](/docs/design/generics/terminology.md#adapting-a-type) `C` and represent +the type of binding that member with either a `C` value or variable. + +```carbon +class __TypeOf_C_Value {} +let __C_Value:! __TypeOf_C_Value = {}; +class __ValueBind_C_Value { + adapt C; +} +class __RefBind_C_Value { + adapt C; +} + +// and similarly for Mutate and Static +``` + +These are the types that result from +[instance binding](/docs/design/expressions/member_access.md#instance-binding) +an instance of `C` with these member names. For example, + +```carbon +let v: C = {.x = 3}; +Assert(v.Value() == 8); +var r: C = {.x = 4}; +Assert(r.Value() == 9); +Assert(r.Mutate() == 6); +``` + +is interpreted as: + +```carbon +let v: C = {.x = 3}; +Assert((v as __ValueBind_C_Value).(『value call()』.Op)() == 8); +var r: C = {.x = 4}; +Assert((r as __RefBind_C_Value).(『mutable call()』.Op)() == 9); +Assert((r as __RefBind_C_Mutate).(『mutable call()』.Op)() == 9); +``` + +How does this arise? + +1. First the simple member access is resolved using the type of the receiver: \ + `v.Value` -> `v.(C.Value)`, `r.Value` -> `r.(C.Value)`, `r.Mutate` -> `r.(C.Mutate)`. + \ + Note that `C.Value` is `__C_Value` with type `__TypeOf_C_Value`. +2. If the left of the `.` is a value expression, the value binding operator is + applied. Otherwise if it is a reference expression, the reference binding + operator is applied. +3. The result of the binding has a type that implements one or both of the call + interfaces. + +The binding operators are defined using two dedicated interfaces: + +```carbon +// For value `x` with type `T` and `y` of type `U`, +// `x.(y)` is `y.(U as ValueBind(T)).『Bind』(x)` +interface ValueBind(T:! type) { + let Result:! type; + fn 『Bind』[self: Self](x: T) -> Result; +} + +// For reference expression `var x: T` and `y` of type `U`, +// `x.(y)` is `*y.(U as RefBind(T)).『Bind』(&x)` +interface RefBind(T:! type) { + let Result:! type; + fn 『Bind』[self: Self](p: T*) -> Result*; +} +``` + +The name `Bind` here is provisional -- it probably should actually be `Op` to be +consistent with other operator interfaces, but I'm using a different name in +this document for now to avoid confusion with the `Op` method of the call +interfaces. + +**Question:** Should the `self` and `T` parameters to bind interfaces be +swapped? + +These binding operations are how we get from `__C_Value` with type +`__TypeOf_C_Value` to `v as __ValueBind_C_Value` or `r as __RefBind_C_Value`: + +```carbon +impl __TypeOf_C_Value as ValueBind(C) + where .Result = __ValueBind_C_Value { + fn 『Bind』[self: Self](x: C) -> __ValueBind_C_Value { + return x as __ValueBind_C_Value; + } +} + +impl __TypeOf_C_Value as RefBind(C) + where .Result = __RefBind_C_Value { + fn 『Bind』[self: Self](p: C*) -> __RefBind_C_Value* { + return p as __RefBind_C_Value*; + } +} +``` + +The last ingredient is the implementation of the call interfaces for these bound +types. + +```carbon +// Since `C.Value` takes `self: Self` it can be used with both +// value and reference expressions: +impl __ValueBind_C_Value as 『value call()』 with .Result = i32; +impl __RefBind_C_Value as 『value call()』 with .Result = i32; +impl __RefBind_C_Value as 『mutable call()』 with .Result = i32; + +// `C.Static` works the same as `C.Value`, except it also +// implements the call interfaces on `__TypeOf_C_Static`. +// This allows `v.Static()` and `r.Static()` to work, in +// addition to `C.Static()`. +impl __ValueBind_C_Static as 『value call()』 with .Result = i32; +impl __RefBind_C_Static as 『value call()』 with .Result = i32; +impl __RefBind_C_Static as 『mutable call()』 with .Result = i32; +impl __TypeOf_C_Static as 『value call()』 where .Result = i32; +impl __TypeOf_C_Static as 『mutable call()』 where .Result = i32; + +// Since `C.Mutate` takes `addr self: Self*`, it requires a +// reference expression. +// +impl __RefBind_C_Mutate as 『mutable call()』 with .Result = i32; +``` + +**Note**: We could reduce the number of implementations required by making +`__RefBind_*` an +[extending adapter](/docs/design/generics/details.md#extending-adapter) of the +corresponding `__ValueBind_*` class. + +### Inheritance and other implicit conversions + +Now consider methods of a base class: + +```carbon +base class B { + fn F[self: Self](); + virtual fn V[self: Self](); +} + +class D { + extend base: B; + impl fn V[self: Self](); +} + +var d: D = {} +d.(B.F)(); +d.(B.V)(); +``` + +To allow this to work, we need the implementation of the bind interfaces to +allow implicit conversions: + +```carbon +impl [T:! ImplicitAs(B)] __TypeOf_B_F as ValueBind(T) + where .Result = __ValueBind_B_F { + fn 『Bind』[self: Self](x: T) -> __ValueBind_B_F { + return (x as B) as __ValueBind_B_F; + } +} + +impl [T:! type where .Self* impls ImplicitAs(B*)] + __TypeOf_B_F as RefBind(T) + where .Result = __RefBind_B_F { + fn 『Bind』[self: Self](p: T*) -> __RefBind_B_F* { + return (p as B*) as __RefBind_B_F*; + } +} +``` + +This matches the expected semantics method calls, even for methods of final +classes. + +Note that the implementation of the bind interfaces is where the `Self` type of +a method is used. If that type is different from the class it is being defined +in, as considered in +[#1345](https://github.com/carbon-language/carbon-lang/issues/1345), that will +be reflected in the bind implementations. + +```carbon +class C { + // Note: not `self: Self` or `self: C`! + fn G[self: Different](); +} + +let c: C = {}; +// `c.G()` is only allowed if there is an implicit +// conversion from `C` to `Different`. + +let d: Different = {}; +// Allowed: +d.(C.G)(); +``` + +results in an implementation using `Different` instead of `C`: + +```carbon +// `C.G` will only bind to values that can implicitly convert +// to type `Different`. +impl [T:! ImplicitAs(Different)] __TypeOf_C_G as ValueBind(T) + where .Result = __ValueBind_C_G; +``` + +### Data fields + +The same `ValueBind` and `RefBind` operations allow us to define access to the +data fields in an object, without any additional changes. + +For example, given a class with a data member `m` with type `i32`: + +```carbon +class C { + var m: i32; +} +``` + +we want the usual operations to work, with `x.m` equivalent to `x.(C.m)`: + +```carbon +let v: C = {.m = 4}; +var x: C = {.m = 3}; +x.m += 5; +Assert(x.(C.m) == v.m + v.(C.m)); +``` + +To accomplish this we will, as before, associate an empty (stateless or +zero-sized) type with the `m` member of `C`, that just exists to support the +binding operation. However, this time the result type of binding is simply +`i32`, the type of the variable, instead of a new, dedicated type. + +```carbon +class __TypeOf_C_m {} +let __C_m:! __TypeOf_C_m = {}; + +impl __TypeOf_C_m as ValueBind(C) where .Result = i32 { + fn 『Bind』[self: Self](x: C) -> i32 { + // Effectively performs `x.m`, but without triggering binding again. + return value_compiler_intrinsic(v, __OffsetOf_C_m, i32) + } +} + +impl __TypeOf_C_Value as RefBind(C) where .Result = i32 { + fn 『Bind』[self: Self](p: C*) -> i32* { + // Effectively performs `&p->m`, but without triggering binding again, + // by doing something like `((p as byte*) + __OffsetOf_C_m) as i32*` + return offset_compiler_intrinsic(p, __OffsetOf_C_m, i32); + } +} +``` + +These definitions give us the desired semantics: + +```carbon +// For value `v` with type `T` and `y` of type `U`, +// `v.(y)` is `y.(U as ValueBind(T)).『Bind』(v)` +v.m == v.(C.m) + == v.(__C_m) + == __C_m.(__TypeOf_C_m as ValueBind(C)).『Bind』(v) + == value_compiler_intrinsic(v, __OffsetOf_C_m, i32) + + +// For reference expression `var x: T` and `y` of type `U`, +// `x.(y)` is `*y.(U as RefBind(T)).『Bind』(&x)` +x.m == x.(C.m) + == x.(__C_m) + == *__C_m.(__TypeOf_C_m as RefBind(C)).『Bind』(&x) + == *offset_compiler_intrinsic(&x, __OffsetOf_C_m, i32) +// Note that this requires `x` to be a reference expression, +// so `&x` is valid, and produces a reference expression, +// since it is the result of dereferencing a pointer. +``` + +### Generic type of a class member + +Given the above, we can now write a constraint on a symbolic parameter to match +the names of an unbound class member. There are a few different cases: + +**Mutating methods** + +```carbon +// `m` can be any method object that implements 『mutable call()』 once bound. +fn CallMutatingMethod + [T:! type, M:! RefBind(T) where .Result impls 『mutable call()』] + (x: T*, m: M) -> auto { + return x->(m)(); +} + +class D { + // Any of these should work: + fn K(); + fn K[self: Self](); + fn K[addr self: Self*](); +} +var d: D; +CallMutatingMethod(&d, D.K); +``` + +FIXME: The above works even if `D.K` takes `self: Self` since even value methods +will implement 『mutable call()』. + +This implementation of `CallMutatingMethod` will work with inheritance and +virtual methods, using +[the support for implicit conversions of self](#inheritance-and-other-implicit-conversions). + +```carbon +base class X { + virtual fn V[self: Self]() -> i32 { return 1; } + fn B[addr self: Self*](); +} +class Y { + extend base: X; + impl fn V[self: Self]() -> i32 { return 2; } +} +class Z { + extend base: X; + impl fn V[self: Self]() -> i32 { return 3; } +} + +var (x: X, y: Y, z: Z); + +// Respects inheritance +CallMutatingMethod(&x, X.B); +CallMutatingMethod(&y, X.B); +CallMutatingMethod(&z, X.B); + +// Respects method overriding +Assert(CallMutatingMethod(&x, X.V) == 1); +Assert(CallMutatingMethod(&y, X.V) == 2); +Assert(CallMutatingMethod(&z, X.V) == 3); +``` + +**Value methods** + +Switching to `ValueBind` and `『value call』` means the receiver object can be +passed by value instead of by pointer: + +```carbon +// `m` can be any method object that implements 『value call()』 once bound. +fn CallValueMethod + [T:! type, M:! ValueBind(T) where .Result impls 『value call()』] + (x: T, m: M) -> auto { + return x.(m)(); +} +``` + +This will work with any value method or static class function, but not with +`addr self` methods. + +**Fields** + +Fields can be accessed, given the type of the field + +```carbon +fn SetField + [T:! type, F:! RefBind(T) where .Result = i32] + (x: T*, f: F, y: i32) { + x->(f) = y; +} + +fn GetField + [T:! type, F:! ValueBind(T) where .Result = i32] + (x: T, f: F) -> i32 { + return x.(f); +} + + +class C { + var m: i32; + var n: i32; +} +var c: C = {.m = 5, .n = 6}; +Assert(GetField(c, C.m) == 5); +Assert(GetField(c, C.n) == 6); +SetField(&c, C.m, 42); +SetField(&c, C.n, 12); +Assert(GetField(c, C.m) == 42); +Assert(GetField(c, C.n) == 12); +``` + +### C++ pointer to member + +In [the generic type of member section](#generic-type-of-a-class-member), the +names of members, such as `D.K`, `X.B`, `X.V`, and `C.n`, refer to zero-sized / +stateless objects where all the offset information is encoded in the type. +However, the definitions of `CallMutatingMethod`, `CallValueMethod`, `SetField`, +and `GetField` do not depend on that fact and will be usable with objects, such +as C++ pointers-to-members, that include the offset information in the runtime +object state. So we can define binding implementations for them so that they may +be used with Carbon's `.(`**`)` and `->(`**`)` operators. + +For example, this is how I would expect C++ code to call the above Carbon +functions: + +```cpp +struct C { + void F() { ++m; } + int m; +}; + +int main() { + // pointer to data member `m` of class C + int C::* p = &C::m; + C c = {2}; + assert(c.*p == 2); + assert(Carbon::GetField(c, p) == 2); + Carbon::SetField(&c, p, 4); + assert(c.m == 4); + // pointer to method `F` of class C + void (C::* q)() = &C::F; + Carbon::CallMutatingMethod(&c, q); + assert(Carbon::GetField(c, &C::m) == 5); +} +``` + +### 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`. + +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 : 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)`. + +**Note:** This mechanism has some potential uses beyond member forwarding. For +example, a class could extend the api of a class it implicitly converts to, see +[inheritance and other implicit conversions](#inheritance-and-other-implicit-conversions). +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. + +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 『Bind』[self: Self](x: Box(T)) -> Result { + // Uses `ValueBind(T).『Bind』` based on type of `U`. + return self.『Bind』(*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 『Bind』[self: Self](p: Box(T)*) -> Result* { + return self.『Bind』(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 `『Bind』` 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 +- 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*`). + +### Future: properties + +If there was a way to implement the binding operator to only produce values, +even when the expression to the left of the `.` was a reference expression, then +that could be used to implement read-only properties. This would support +something like: + +```carbon +let Pi: f64 = 3.1415926535897932384626433832795; + +class Circle { + var radius: f64; + read_property area -> f64 { + return Pi * self.radius * self.radius; + } +} + +let c: Circle = {.radius = 2}; +Assert(NearlyEqual(c.area, 4 * Pi)); +``` + +In this example, the binding of `c` of type `Circle` to `Circle.area` would +perform the computation and return the result as an `f64`. + +If there was some way to customize the result of binding, this could be extended +to support other kinds of properties, such as mutable properties that have both +`get` and `set` methods. The main obstacle to any support for properties with +binding is how the customization would be done. The most natural way to support +this customization would be to have multiple interfaces. The compiler would try +them in a specified order and use the one it found first. This has the downside +of the possibility of different behavior in a checked generic context where only +some of the implementations are visible. + ## Rationale TODO: How does this proposal effectively advance Carbon's goals? Rather than From 8f6bb0cfc01525853a11ad09c543559b516e5f43 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 23 Feb 2024 17:47:52 +0000 Subject: [PATCH 03/77] Checkpoint progress. --- proposals/p3720.md | 64 +++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index b3d4dc70754fa..bf97868ca7c4f 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -104,7 +104,7 @@ is interpreted as: ```carbon let v: C = {.x = 3}; -Assert((v as __ValueBind_C_Value).(『value call()』.Op)() == 8); +Assert((v as __ValueBind_C_Value).(Call(()).Op)() == 8); var r: C = {.x = 4}; Assert((r as __RefBind_C_Value).(『mutable call()』.Op)() == 9); Assert((r as __RefBind_C_Mutate).(『mutable call()』.Op)() == 9); @@ -126,17 +126,17 @@ The binding operators are defined using two dedicated interfaces: ```carbon // For value `x` with type `T` and `y` of type `U`, -// `x.(y)` is `y.(U as ValueBind(T)).『Bind』(x)` +// `x.(y)` is `y.(U as ValueBind(T)).Op(x)` interface ValueBind(T:! type) { let Result:! type; - fn 『Bind』[self: Self](x: T) -> Result; + fn Op[self: Self](x: T) -> Result; } // For reference expression `var x: T` and `y` of type `U`, -// `x.(y)` is `*y.(U as RefBind(T)).『Bind』(&x)` +// `x.(y)` is `*y.(U as RefBind(T)).Op(&x)` interface RefBind(T:! type) { let Result:! type; - fn 『Bind』[self: Self](p: T*) -> Result*; + fn Op[self: Self](p: T*) -> Result*; } ``` @@ -154,14 +154,14 @@ These binding operations are how we get from `__C_Value` with type ```carbon impl __TypeOf_C_Value as ValueBind(C) where .Result = __ValueBind_C_Value { - fn 『Bind』[self: Self](x: C) -> __ValueBind_C_Value { + fn Op[self: Self](x: C) -> __ValueBind_C_Value { return x as __ValueBind_C_Value; } } impl __TypeOf_C_Value as RefBind(C) where .Result = __RefBind_C_Value { - fn 『Bind』[self: Self](p: C*) -> __RefBind_C_Value* { + fn Op[self: Self](p: C*) -> __RefBind_C_Value* { return p as __RefBind_C_Value*; } } @@ -173,23 +173,23 @@ types. ```carbon // Since `C.Value` takes `self: Self` it can be used with both // value and reference expressions: -impl __ValueBind_C_Value as 『value call()』 with .Result = i32; -impl __RefBind_C_Value as 『value call()』 with .Result = i32; +impl __ValueBind_C_Value as Call(()) with .Result = i32; +impl __RefBind_C_Value as Call(()) with .Result = i32; impl __RefBind_C_Value as 『mutable call()』 with .Result = i32; // `C.Static` works the same as `C.Value`, except it also // implements the call interfaces on `__TypeOf_C_Static`. // This allows `v.Static()` and `r.Static()` to work, in // addition to `C.Static()`. -impl __ValueBind_C_Static as 『value call()』 with .Result = i32; -impl __RefBind_C_Static as 『value call()』 with .Result = i32; +impl __ValueBind_C_Static as Call(()) with .Result = i32; +impl __RefBind_C_Static as Call(()) with .Result = i32; impl __RefBind_C_Static as 『mutable call()』 with .Result = i32; -impl __TypeOf_C_Static as 『value call()』 where .Result = i32; +impl __TypeOf_C_Static as Call(()) where .Result = i32; impl __TypeOf_C_Static as 『mutable call()』 where .Result = i32; // Since `C.Mutate` takes `addr self: Self*`, it requires a // reference expression. -// +// impl __RefBind_C_Mutate as 『mutable call()』 with .Result = i32; ``` @@ -224,7 +224,7 @@ allow implicit conversions: ```carbon impl [T:! ImplicitAs(B)] __TypeOf_B_F as ValueBind(T) where .Result = __ValueBind_B_F { - fn 『Bind』[self: Self](x: T) -> __ValueBind_B_F { + fn Op[self: Self](x: T) -> __ValueBind_B_F { return (x as B) as __ValueBind_B_F; } } @@ -232,7 +232,7 @@ impl [T:! ImplicitAs(B)] __TypeOf_B_F as ValueBind(T) impl [T:! type where .Self* impls ImplicitAs(B*)] __TypeOf_B_F as RefBind(T) where .Result = __RefBind_B_F { - fn 『Bind』[self: Self](p: T*) -> __RefBind_B_F* { + fn Op[self: Self](p: T*) -> __RefBind_B_F* { return (p as B*) as __RefBind_B_F*; } } @@ -303,14 +303,14 @@ class __TypeOf_C_m {} let __C_m:! __TypeOf_C_m = {}; impl __TypeOf_C_m as ValueBind(C) where .Result = i32 { - fn 『Bind』[self: Self](x: C) -> i32 { + fn Op[self: Self](x: C) -> i32 { // Effectively performs `x.m`, but without triggering binding again. return value_compiler_intrinsic(v, __OffsetOf_C_m, i32) } } impl __TypeOf_C_Value as RefBind(C) where .Result = i32 { - fn 『Bind』[self: Self](p: C*) -> i32* { + fn Op[self: Self](p: C*) -> i32* { // Effectively performs `&p->m`, but without triggering binding again, // by doing something like `((p as byte*) + __OffsetOf_C_m) as i32*` return offset_compiler_intrinsic(p, __OffsetOf_C_m, i32); @@ -322,18 +322,18 @@ These definitions give us the desired semantics: ```carbon // For value `v` with type `T` and `y` of type `U`, -// `v.(y)` is `y.(U as ValueBind(T)).『Bind』(v)` +// `v.(y)` is `y.(U as ValueBind(T)).Op(v)` v.m == v.(C.m) == v.(__C_m) - == __C_m.(__TypeOf_C_m as ValueBind(C)).『Bind』(v) + == __C_m.(__TypeOf_C_m as ValueBind(C)).Op(v) == value_compiler_intrinsic(v, __OffsetOf_C_m, i32) // For reference expression `var x: T` and `y` of type `U`, -// `x.(y)` is `*y.(U as RefBind(T)).『Bind』(&x)` +// `x.(y)` is `*y.(U as RefBind(T)).Op(&x)` x.m == x.(C.m) == x.(__C_m) - == *__C_m.(__TypeOf_C_m as RefBind(C)).『Bind』(&x) + == *__C_m.(__TypeOf_C_m as RefBind(C)).Op(&x) == *offset_compiler_intrinsic(&x, __OffsetOf_C_m, i32) // Note that this requires `x` to be a reference expression, // so `&x` is valid, and produces a reference expression, @@ -405,9 +405,9 @@ Switching to `ValueBind` and `『value call』` means the receiver object can be passed by value instead of by pointer: ```carbon -// `m` can be any method object that implements 『value call()』 once bound. +// `m` can be any method object that implements Call(()) once bound. fn CallValueMethod - [T:! type, M:! ValueBind(T) where .Result impls 『value call()』] + [T:! type, M:! ValueBind(T) where .Result impls Call(())] (x: T, m: M) -> auto { return x.(m)(); } @@ -555,9 +555,9 @@ 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 『Bind』[self: Self](x: Box(T)) -> Result { - // Uses `ValueBind(T).『Bind』` based on type of `U`. - return self.『Bind』(*x.ptr); + 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. @@ -566,8 +566,8 @@ impl forall [T:! type, U:! ValueBind(T)] impl forall [T:! type, U:! RefBind(T)] U as RefBind(Box(T)) where .Result = U.Result { - fn 『Bind』[self: Self](p: Box(T)*) -> Result* { - return self.『Bind』(p->ptr); + fn Op[self: Self](p: Box(T)*) -> Result* { + return self.Op(p->ptr); // Or equivalently: // return p->ptr->(self); } @@ -579,10 +579,10 @@ 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 `『Bind』` 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 +- 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`. From a79029ce9a434b2421e65345fca5784864262aa7 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sun, 25 Feb 2024 03:54:29 +0000 Subject: [PATCH 04/77] Abstract, problem, and work on proposal and details --- proposals/p3720.md | 92 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index bf97868ca7c4f..4ba73801d2e5d 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -30,29 +30,101 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Abstract -TODO: Describe, in a succinct paragraph, the gist of this document. This -paragraph should be reproduced verbatim in the PR summary. +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` of other classes, for forwarding +use cases. ## Problem -TODO: What problem are you trying to solve? How important is that problem? Who -is impacted by it? +What happens when binding is performed between an object instance and a member +of its type? We'd like to define the semantics in a way that is simple, +orthogonal, supports the use cases from C++, allows users to express their +intent in code in a natural and predictable way that is consistent with other +Carbon constructs, and is consistent with Carbon's goals. + +Consider a class with a method and a field: + +```carbon +class C { + fn M[self: Self](); + var f: i32; +} +var x: C = {.f = 2}; +``` + +The expressions `C.M` and `C.f` correspond roughly to +[C++ pointers to members](https://en.cppreference.com/w/cpp/language/pointer#Pointers_to_members). +They may be used to access the members of `x` using Carbon's +[compound member syntax](/docs/design/expressions/member_access.md), as in +`x.(C.M)` or `x.(C.f)`. What is their type? Can they be passed to a function +separately from the instance of `C` to bind with it? ## Background +FIXME: Is there any background that readers should consider to fully understand +this problem and your approach to solving it? + - [Qualified names and member access](/docs/design/expressions/member_access.md) - [Proposal #989: Member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) - [Proposal #2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) - [Proposal #2875: Functions, function types, and function calls](https://github.com/carbon-language/carbon-lang/pull/2875) -TODO: Is there any background that readers should consider to fully understand -this problem and your approach to solving it? +The currently accepted proposals for functions don't support all of the +different function signatures for the `Call` interface. For example, it does not +support `addr self` or explicit compile-time parameters. That is out of scope of +this proposal, and will be addressed separately. The difference between +functions and methods, however, is in scope. ## Proposal -TODO: Briefly and at a high level, how do you propose to solve the problem? Why +FIXME: Briefly and at a high level, how do you propose to solve the problem? Why will that in fact solve it? +```carbon +// For value `x` with type `T` and `y` of type `U`, +// `x.(y)` is `y.(U as ValueBind(T)).Op(x)` +interface ValueBind(T:! type) { + let Result:! type; + fn Op[self: Self](x: T) -> Result; +} + +// For reference expression `var x: T` and `y` of type `U`, +// `x.(y)` is `*y.(U as RefBind(T)).Op(&x)` +interface RefBind(T:! type) { + let Result:! type; + fn Op[self: Self](p: T*) -> Result*; +} +``` + +`extend api T`: + +```carbon +class Extended { + fn F[self: Self](); + fn G[self: Self](); +} + +class Extending { + fn G[self: Self](); + extend api Extended; +} + +var e: Extended +``` + +This means that lookup into `Extending` also looks in the type `Extended` for +members. In this way, `e.F` will find `e.(Extended.F)`. `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; +} +``` + ## Details TODO: Fully explain the details of the proposed solution. @@ -542,6 +614,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)`. +**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. + **Note:** This mechanism has some potential uses beyond member forwarding. For example, a class could extend the api of a class it implicitly converts to, see [inheritance and other implicit conversions](#inheritance-and-other-implicit-conversions). From 764e129616aa6a86bfa2a8fc9706599651ab8b8c Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 26 Feb 2024 22:57:30 +0000 Subject: [PATCH 05/77] Checkpoint progress. --- proposals/p3720.md | 205 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 157 insertions(+), 48 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 4ba73801d2e5d..dc75ac7b2b925 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -97,6 +97,25 @@ interface RefBind(T:! type) { } ``` +> **FIXME: Question:** Should the `self` and `T` parameters to bind interfaces +> be swapped? +> +> ```carbon +> // For value `x` with type `T` and `y` of type `U`, +> // `x.(y)` is `x.(T as ValueBind(U)).Op(y)` +> interface ValueBind(U:! type) { +> let Result:! type; +> fn Op[self: Self](x: U) -> Result; +> } +> +> // For reference expression `var x: T` and `y` of type `U`, +> // `x.(y)` is `*x.(T as RefBind(U)).Op(y)` +> interface RefBind(U:! type) { +> let Result:! type; +> fn Op[addr self: Self*](x: U) -> Result*; +> } +> ``` + `extend api T`: ```carbon @@ -212,14 +231,29 @@ interface RefBind(T:! type) { } ``` +> **FIXME, Alternative:** +> +> ```carbon +> // For value `x` with type `T` and `y` of type `U`, +> // `x.(y)` is `x.(T as ValueBind(U)).Op(y)` +> interface ValueBind(U:! type) { +> let Result:! type; +> fn Op[self: Self](x: U) -> Result; +> } +> +> // For reference expression `var x: T` and `y` of type `U`, +> // `x.(y)` is `*x.(T as RefBind(U)).Op(y)` +> interface RefBind(U:! type) { +> let Result:! type; +> fn Op[addr self: Self*](x: U) -> Result*; +> } +> ``` + The name `Bind` here is provisional -- it probably should actually be `Op` to be consistent with other operator interfaces, but I'm using a different name in this document for now to avoid confusion with the `Op` method of the call interfaces. -**Question:** Should the `self` and `T` parameters to bind interfaces be -swapped? - These binding operations are how we get from `__C_Value` with type `__TypeOf_C_Value` to `v as __ValueBind_C_Value` or `r as __RefBind_C_Value`: @@ -239,6 +273,24 @@ impl __TypeOf_C_Value as RefBind(C) } ``` +> **FIXME, Alternative:** +> +> ```carbon +> impl C as ValueBind(__TypeOf_C_Value) +> where .Result = __ValueBind_C_Value { +> fn Op[self: Self](_: __TypeOf_C_Value) -> __ValueBind_C_Value { +> return self as __ValueBind_C_Value; +> } +> } +> +> impl C as RefBind(__TypeOf_C_Value) +> where .Result = __RefBind_C_Value { +> fn Op[addr self: Self*](_: __TypeOf_C_Value) -> __RefBind_C_Value* { +> return self as __RefBind_C_Value*; +> } +> } +> ``` + The last ingredient is the implementation of the call interfaces for these bound types. @@ -247,7 +299,6 @@ types. // value and reference expressions: impl __ValueBind_C_Value as Call(()) with .Result = i32; impl __RefBind_C_Value as Call(()) with .Result = i32; -impl __RefBind_C_Value as 『mutable call()』 with .Result = i32; // `C.Static` works the same as `C.Value`, except it also // implements the call interfaces on `__TypeOf_C_Static`. @@ -255,14 +306,7 @@ impl __RefBind_C_Value as 『mutable call()』 with .Result = i32; // addition to `C.Static()`. impl __ValueBind_C_Static as Call(()) with .Result = i32; impl __RefBind_C_Static as Call(()) with .Result = i32; -impl __RefBind_C_Static as 『mutable call()』 with .Result = i32; impl __TypeOf_C_Static as Call(()) where .Result = i32; -impl __TypeOf_C_Static as 『mutable call()』 where .Result = i32; - -// Since `C.Mutate` takes `addr self: Self*`, it requires a -// reference expression. -// -impl __RefBind_C_Mutate as 『mutable call()』 with .Result = i32; ``` **Note**: We could reduce the number of implementations required by making @@ -310,6 +354,25 @@ impl [T:! type where .Self* impls ImplicitAs(B*)] } ``` +> **FIXME, Alternative:** +> +> ```carbon +> impl [T:! ImplicitAs(B)] T as ValueBind(__TypeOf_B_F) +> where .Result = __ValueBind_B_F { +> fn Op[self: Self](x: __TypeOf_B_F) -> __ValueBind_B_F { +> return (self as B) as __ValueBind_B_F; +> } +> } +> +> impl [T:! type where .Self* impls ImplicitAs(B*)] +> T as RefBind(__TypeOf_B_F) +> where .Result = __RefBind_B_F { +> fn Op[addr self: Self*](x: __TypeOf_B_F) -> __RefBind_B_F* { +> return (self as B*) as __RefBind_B_F*; +> } +> } +> ``` + This matches the expected semantics method calls, even for methods of final classes. @@ -343,6 +406,15 @@ impl [T:! ImplicitAs(Different)] __TypeOf_C_G as ValueBind(T) where .Result = __ValueBind_C_G; ``` +> **FIXME, Alternative:** +> +> ```carbon +> // `C.G` will only bind to values that can implicitly convert +> // to type `Different`. +> impl [T:! ImplicitAs(Different)] T as ValueBind(__TypeOf_C_G) +> where .Result = __ValueBind_C_G; +> ``` + ### Data fields The same `ValueBind` and `RefBind` operations allow us to define access to the @@ -381,7 +453,7 @@ impl __TypeOf_C_m as ValueBind(C) where .Result = i32 { } } -impl __TypeOf_C_Value as RefBind(C) where .Result = i32 { +impl __TypeOf_C_m as RefBind(C) where .Result = i32 { fn Op[self: Self](p: C*) -> i32* { // Effectively performs `&p->m`, but without triggering binding again, // by doing something like `((p as byte*) + __OffsetOf_C_m) as i32*` @@ -390,6 +462,28 @@ impl __TypeOf_C_Value as RefBind(C) where .Result = i32 { } ``` +> **FIXME, Alternative:** +> +> ```carbon +> class __TypeOf_C_m {} +> let __C_m:! __TypeOf_C_m = {}; +> +> impl C as ValueBind(__TypeOf_C_m) where .Result = i32 { +> fn Op[self: Self](x: C) -> i32 { +> // Effectively performs `x.m`, but without triggering binding again. +> return value_compiler_intrinsic(v, __OffsetOf_C_m, i32) +> } +> } +> +> impl C as RefBind(__TypeOf_C_m) where .Result = i32 { +> fn Op[self: Self](p: C*) -> i32* { +> // Effectively performs `&p->m`, but without triggering binding again, +> // by doing something like `((p as byte*) + __OffsetOf_C_m) as i32*` +> return offset_compiler_intrinsic(p, __OffsetOf_C_m, i32); +> } +> } +> ``` + These definitions give us the desired semantics: ```carbon @@ -412,33 +506,66 @@ x.m == x.(C.m) // since it is the result of dereferencing a pointer. ``` +> **FIXME, Alternative:** +> +> ```carbon +> // For value `v` with type `T` and `y` of type `U`, +> // `v.(y)` is `v.(T as ValueBind(U)).Op(y)` +> v.m == v.(C.m) +> == v.(__C_m) +> == v.(C as ValueBind(__TypeOf_C_m)).Op(__C_m) +> == value_compiler_intrinsic(v, __OffsetOf_C_m, i32) +> +> +> // For reference expression `var x: T` and `y` of type `U`, +> // `x.(y)` is `*x.(T as RefBind(U)).Op(y)` +> x.m == x.(C.m) +> == x.(__C_m) +> == *x.(C as RefBind(__TypeOf_C_mC)).Op(__C_m) +> == *offset_compiler_intrinsic(&x, __OffsetOf_C_m, i32) +> // Note that this requires `x` to be a reference expression, +> // so `x` may be the receiver passed to an `addr self` method, +> // and produces a reference expression, since it is the result +> // of dereferencing a pointer. +> ``` + ### Generic type of a class member Given the above, we can now write a constraint on a symbolic parameter to match -the names of an unbound class member. There are a few different cases: +the names of an unbound class member. There are a couple of different cases: -**Mutating methods** +**Methods** -```carbon -// `m` can be any method object that implements 『mutable call()』 once bound. -fn CallMutatingMethod - [T:! type, M:! RefBind(T) where .Result impls 『mutable call()』] - (x: T*, m: M) -> auto { - return x->(m)(); -} +Restricting to value methods, since mutating (`addr self`) methods are out of +scope for this proposal, the receiver object may be passed by value. To be able +to call the method, we must include a restriction that the result of `ValueBind` +implements `Call(())`: -class D { - // Any of these should work: - fn K(); - fn K[self: Self](); - fn K[addr self: Self*](); +```carbon +// `m` can be any method object that implements `Call(())` once bound. +fn CallValueMethod + [T:! type, M:! ValueBind(T) where .Result impls Call(())] + (x: T, m: M) -> auto { + return x.(m)(); } -var d: D; -CallMutatingMethod(&d, D.K); ``` -FIXME: The above works even if `D.K` takes `self: Self` since even value methods -will implement 『mutable call()』. +> **FIXME, Alternative:** +> +> ```carbon +> // `m` can be any method object that implements `Call(())` once bound. +> fn CallValueMethod +> [M:! type, T:! ValueBind(M) where .Result impls Call(())] +> (x: T, m: M) -> auto { +> return x.(m)(); +> } +> ``` +> +> Note: might be awkward if multiple methods. + +This will work with any value method or static class function. + +**FIXME: Left off here.** This implementation of `CallMutatingMethod` will work with inheritance and virtual methods, using @@ -471,23 +598,6 @@ Assert(CallMutatingMethod(&y, X.V) == 2); Assert(CallMutatingMethod(&z, X.V) == 3); ``` -**Value methods** - -Switching to `ValueBind` and `『value call』` means the receiver object can be -passed by value instead of by pointer: - -```carbon -// `m` can be any method object that implements Call(()) once bound. -fn CallValueMethod - [T:! type, M:! ValueBind(T) where .Result impls Call(())] - (x: T, m: M) -> auto { - return x.(m)(); -} -``` - -This will work with any value method or static class function, but not with -`addr self` methods. - **Fields** Fields can be accessed, given the type of the field @@ -505,7 +615,6 @@ fn GetField return x.(f); } - class C { var m: i32; var n: i32; From 76a9824334f32c950f091f0c5421e5ad5c7d2c55 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 27 Feb 2024 00:25:27 +0000 Subject: [PATCH 06/77] Checkpoint progress. --- proposals/p3720.md | 61 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index dc75ac7b2b925..99f524653b22b 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -217,14 +217,14 @@ The binding operators are defined using two dedicated interfaces: ```carbon // For value `x` with type `T` and `y` of type `U`, -// `x.(y)` is `y.(U as ValueBind(T)).Op(x)` +// `x.(y)` is `y.((U as ValueBind(T)).Op)(x)` interface ValueBind(T:! type) { let Result:! type; fn Op[self: Self](x: T) -> Result; } // For reference expression `var x: T` and `y` of type `U`, -// `x.(y)` is `*y.(U as RefBind(T)).Op(&x)` +// `x.(y)` is `*y.((U as RefBind(T)).Op)(&x)` interface RefBind(T:! type) { let Result:! type; fn Op[self: Self](p: T*) -> Result*; @@ -235,14 +235,14 @@ interface RefBind(T:! type) { > > ```carbon > // For value `x` with type `T` and `y` of type `U`, -> // `x.(y)` is `x.(T as ValueBind(U)).Op(y)` +> // `x.(y)` is `x.((T as ValueBind(U)).Op)(y)` > interface ValueBind(U:! type) { > let Result:! type; > fn Op[self: Self](x: U) -> Result; > } > > // For reference expression `var x: T` and `y` of type `U`, -> // `x.(y)` is `*x.(T as RefBind(U)).Op(y)` +> // `x.(y)` is `*x.((T as RefBind(U)).Op)(y)` > interface RefBind(U:! type) { > let Result:! type; > fn Op[addr self: Self*](x: U) -> Result*; @@ -491,10 +491,10 @@ These definitions give us the desired semantics: // `v.(y)` is `y.(U as ValueBind(T)).Op(v)` v.m == v.(C.m) == v.(__C_m) + == v.(__C_m as (__TypeOf_C_m as ValueBind(C))) == __C_m.(__TypeOf_C_m as ValueBind(C)).Op(v) == value_compiler_intrinsic(v, __OffsetOf_C_m, i32) - // For reference expression `var x: T` and `y` of type `U`, // `x.(y)` is `*y.(U as RefBind(T)).Op(&x)` x.m == x.(C.m) @@ -510,18 +510,17 @@ x.m == x.(C.m) > > ```carbon > // For value `v` with type `T` and `y` of type `U`, -> // `v.(y)` is `v.(T as ValueBind(U)).Op(y)` +> // `v.(y)` is `v.((T as ValueBind(U)).Op)(y)` > v.m == v.(C.m) > == v.(__C_m) -> == v.(C as ValueBind(__TypeOf_C_m)).Op(__C_m) +> == v.((C as ValueBind(__TypeOf_C_m)).Op)(__C_m) > == value_compiler_intrinsic(v, __OffsetOf_C_m, i32) > -> > // For reference expression `var x: T` and `y` of type `U`, -> // `x.(y)` is `*x.(T as RefBind(U)).Op(y)` +> // `x.(y)` is `*x.((T as RefBind(U)).Op)(y)` > x.m == x.(C.m) > == x.(__C_m) -> == *x.(C as RefBind(__TypeOf_C_mC)).Op(__C_m) +> == *x.((C as RefBind(__TypeOf_C_mC)).Op)(__C_m) > == *offset_compiler_intrinsic(&x, __OffsetOf_C_m, i32) > // Note that this requires `x` to be a reference expression, > // so `x` may be the receiver passed to an `addr self` method, @@ -761,6 +760,43 @@ impl forall [T:! type, U:! RefBind(T)] } ``` +> **FIXME, Alternative:** +> +> ```carbon +> impl forall [U:! type, T:! ValueBind(U)] +> Box(T) as ValueBind(U) where .Result = T.Result { +> fn Op[self: Self](x: U) -> Result { +> // Uses `ValueBind(U).Op` based on type of `T`. +> return self.ptr->Op(x); +> +> // NOT `return self.ptr->(x)` since that attempts +> // to do reference binding not value binding. +> } +> } +> +> impl forall [U:! type, T:! RefBind(U)] +> Box(T) as RefBind(U) where .Result = T.Result { +> fn Op[addr self: Self*](x: U) -> Result* { +> return self->ptr->Op(x); +> // Or equivalently: +> // return self->ptr->(x); +> } +> } +> ``` +> +> In this case we have an additional option to write the binding implementation +> inline in the class: +> +> ```carbon +> class Box(T:! type) { +> var ptr: T*; +> impl forall [U:! type where T impls ValueBind(.Self)] +> as ValueBind(U) where .Result = (T as ValueBind(U)).Result { +> // ... +> } +> } +> ``` + A few observations: - These declarations use `Box` to satisfy the orphan rule, and so this @@ -834,3 +870,8 @@ For example: ## Alternatives considered TODO: What alternative solutions have you considered? + +FIXME: two ways to define the interface rewrite + +- Match order of arguments +- Bind is a property of the member From 7bfdd29d5c1d5e6e8b5fa3e3ffafa20d40b21c18 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 27 Feb 2024 04:16:33 +0000 Subject: [PATCH 07/77] Reject alternative --- proposals/p3720.md | 257 +++++++++++++-------------------------------- 1 file changed, 74 insertions(+), 183 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 99f524653b22b..da3e9b6b00740 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -25,6 +25,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Future: properties](#future-properties) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) + - [Swap the bind interface parameters](#swap-the-bind-interface-parameters) @@ -83,39 +84,20 @@ will that in fact solve it? ```carbon // For value `x` with type `T` and `y` of type `U`, -// `x.(y)` is `y.(U as ValueBind(T)).Op(x)` +// `x.(y)` is `y.((U as ValueBind(T)).Op)(x)` interface ValueBind(T:! type) { let Result:! type; fn Op[self: Self](x: T) -> Result; } // For reference expression `var x: T` and `y` of type `U`, -// `x.(y)` is `*y.(U as RefBind(T)).Op(&x)` +// `x.(y)` is `*y.((U as RefBind(T)).Op)(&x)` interface RefBind(T:! type) { let Result:! type; fn Op[self: Self](p: T*) -> Result*; } ``` -> **FIXME: Question:** Should the `self` and `T` parameters to bind interfaces -> be swapped? -> -> ```carbon -> // For value `x` with type `T` and `y` of type `U`, -> // `x.(y)` is `x.(T as ValueBind(U)).Op(y)` -> interface ValueBind(U:! type) { -> let Result:! type; -> fn Op[self: Self](x: U) -> Result; -> } -> -> // For reference expression `var x: T` and `y` of type `U`, -> // `x.(y)` is `*x.(T as RefBind(U)).Op(y)` -> interface RefBind(U:! type) { -> let Result:! type; -> fn Op[addr self: Self*](x: U) -> Result*; -> } -> ``` - `extend api T`: ```carbon @@ -231,24 +213,6 @@ interface RefBind(T:! type) { } ``` -> **FIXME, Alternative:** -> -> ```carbon -> // For value `x` with type `T` and `y` of type `U`, -> // `x.(y)` is `x.((T as ValueBind(U)).Op)(y)` -> interface ValueBind(U:! type) { -> let Result:! type; -> fn Op[self: Self](x: U) -> Result; -> } -> -> // For reference expression `var x: T` and `y` of type `U`, -> // `x.(y)` is `*x.((T as RefBind(U)).Op)(y)` -> interface RefBind(U:! type) { -> let Result:! type; -> fn Op[addr self: Self*](x: U) -> Result*; -> } -> ``` - The name `Bind` here is provisional -- it probably should actually be `Op` to be consistent with other operator interfaces, but I'm using a different name in this document for now to avoid confusion with the `Op` method of the call @@ -273,24 +237,6 @@ impl __TypeOf_C_Value as RefBind(C) } ``` -> **FIXME, Alternative:** -> -> ```carbon -> impl C as ValueBind(__TypeOf_C_Value) -> where .Result = __ValueBind_C_Value { -> fn Op[self: Self](_: __TypeOf_C_Value) -> __ValueBind_C_Value { -> return self as __ValueBind_C_Value; -> } -> } -> -> impl C as RefBind(__TypeOf_C_Value) -> where .Result = __RefBind_C_Value { -> fn Op[addr self: Self*](_: __TypeOf_C_Value) -> __RefBind_C_Value* { -> return self as __RefBind_C_Value*; -> } -> } -> ``` - The last ingredient is the implementation of the call interfaces for these bound types. @@ -354,25 +300,6 @@ impl [T:! type where .Self* impls ImplicitAs(B*)] } ``` -> **FIXME, Alternative:** -> -> ```carbon -> impl [T:! ImplicitAs(B)] T as ValueBind(__TypeOf_B_F) -> where .Result = __ValueBind_B_F { -> fn Op[self: Self](x: __TypeOf_B_F) -> __ValueBind_B_F { -> return (self as B) as __ValueBind_B_F; -> } -> } -> -> impl [T:! type where .Self* impls ImplicitAs(B*)] -> T as RefBind(__TypeOf_B_F) -> where .Result = __RefBind_B_F { -> fn Op[addr self: Self*](x: __TypeOf_B_F) -> __RefBind_B_F* { -> return (self as B*) as __RefBind_B_F*; -> } -> } -> ``` - This matches the expected semantics method calls, even for methods of final classes. @@ -406,15 +333,6 @@ impl [T:! ImplicitAs(Different)] __TypeOf_C_G as ValueBind(T) where .Result = __ValueBind_C_G; ``` -> **FIXME, Alternative:** -> -> ```carbon -> // `C.G` will only bind to values that can implicitly convert -> // to type `Different`. -> impl [T:! ImplicitAs(Different)] T as ValueBind(__TypeOf_C_G) -> where .Result = __ValueBind_C_G; -> ``` - ### Data fields The same `ValueBind` and `RefBind` operations allow us to define access to the @@ -462,28 +380,6 @@ impl __TypeOf_C_m as RefBind(C) where .Result = i32 { } ``` -> **FIXME, Alternative:** -> -> ```carbon -> class __TypeOf_C_m {} -> let __C_m:! __TypeOf_C_m = {}; -> -> impl C as ValueBind(__TypeOf_C_m) where .Result = i32 { -> fn Op[self: Self](x: C) -> i32 { -> // Effectively performs `x.m`, but without triggering binding again. -> return value_compiler_intrinsic(v, __OffsetOf_C_m, i32) -> } -> } -> -> impl C as RefBind(__TypeOf_C_m) where .Result = i32 { -> fn Op[self: Self](p: C*) -> i32* { -> // Effectively performs `&p->m`, but without triggering binding again, -> // by doing something like `((p as byte*) + __OffsetOf_C_m) as i32*` -> return offset_compiler_intrinsic(p, __OffsetOf_C_m, i32); -> } -> } -> ``` - These definitions give us the desired semantics: ```carbon @@ -506,28 +402,6 @@ x.m == x.(C.m) // since it is the result of dereferencing a pointer. ``` -> **FIXME, Alternative:** -> -> ```carbon -> // For value `v` with type `T` and `y` of type `U`, -> // `v.(y)` is `v.((T as ValueBind(U)).Op)(y)` -> v.m == v.(C.m) -> == v.(__C_m) -> == v.((C as ValueBind(__TypeOf_C_m)).Op)(__C_m) -> == value_compiler_intrinsic(v, __OffsetOf_C_m, i32) -> -> // For reference expression `var x: T` and `y` of type `U`, -> // `x.(y)` is `*x.((T as RefBind(U)).Op)(y)` -> x.m == x.(C.m) -> == x.(__C_m) -> == *x.((C as RefBind(__TypeOf_C_mC)).Op)(__C_m) -> == *offset_compiler_intrinsic(&x, __OffsetOf_C_m, i32) -> // Note that this requires `x` to be a reference expression, -> // so `x` may be the receiver passed to an `addr self` method, -> // and produces a reference expression, since it is the result -> // of dereferencing a pointer. -> ``` - ### Generic type of a class member Given the above, we can now write a constraint on a symbolic parameter to match @@ -549,19 +423,6 @@ fn CallValueMethod } ``` -> **FIXME, Alternative:** -> -> ```carbon -> // `m` can be any method object that implements `Call(())` once bound. -> fn CallValueMethod -> [M:! type, T:! ValueBind(M) where .Result impls Call(())] -> (x: T, m: M) -> auto { -> return x.(m)(); -> } -> ``` -> -> Note: might be awkward if multiple methods. - This will work with any value method or static class function. **FIXME: Left off here.** @@ -760,43 +621,6 @@ impl forall [T:! type, U:! RefBind(T)] } ``` -> **FIXME, Alternative:** -> -> ```carbon -> impl forall [U:! type, T:! ValueBind(U)] -> Box(T) as ValueBind(U) where .Result = T.Result { -> fn Op[self: Self](x: U) -> Result { -> // Uses `ValueBind(U).Op` based on type of `T`. -> return self.ptr->Op(x); -> -> // NOT `return self.ptr->(x)` since that attempts -> // to do reference binding not value binding. -> } -> } -> -> impl forall [U:! type, T:! RefBind(U)] -> Box(T) as RefBind(U) where .Result = T.Result { -> fn Op[addr self: Self*](x: U) -> Result* { -> return self->ptr->Op(x); -> // Or equivalently: -> // return self->ptr->(x); -> } -> } -> ``` -> -> In this case we have an additional option to write the binding implementation -> inline in the class: -> -> ```carbon -> class Box(T:! type) { -> var ptr: T*; -> impl forall [U:! type where T impls ValueBind(.Self)] -> as ValueBind(U) where .Result = (T as ValueBind(U)).Result { -> // ... -> } -> } -> ``` - A few observations: - These declarations use `Box` to satisfy the orphan rule, and so this @@ -869,9 +693,76 @@ For example: ## Alternatives considered -TODO: What alternative solutions have you considered? +### Swap the bind interface parameters -FIXME: two ways to define the interface rewrite +We considered instead making the receiver object the `Self` type of the +interface, and using the member type as the parameter to the interface. This +would have the advantage of matching the order that they appear in the source, +consistent with other operators. -- Match order of arguments -- Bind is a property of the member +> **Alternative:** +> +> ```carbon +> // For value `x` with type `T` and `y` of type `U`, +> // `x.(y)` is `x.((T as ValueBind(U)).Op)(y)` +> interface ValueBind(U:! type) { +> let Result:! type; +> fn Op[self: Self](x: U) -> Result; +> } +> +> // For reference expression `var x: T` and `y` of type `U`, +> // `x.(y)` is `*x.((T as RefBind(U)).Op)(y)` +> interface RefBind(U:! type) { +> let Result:! type; +> fn Op[addr self: Self*](x: U) -> Result*; +> } +> ``` + +This had some disadvantages however: + +- The binding property is more associated with the member than the receiver. +- Some patterns are more awkward in the alternative syntax. + +As an example of this last point, consider a function that takes multiple (or +even a variadic list) methods to call on a receiver object. With the proposed +approach, each method type is constrained: + +```carbon +// `m1`, `m2`, and `m3` are methods on class `T`. +fn Call3Methods[T:! type, + M1:! ValueBind(T) where .Result impls Call(()), + M2:! ValueBind(T) where .Result impls Call(()), + M3:! ValueBind(T) where .Result impls Call(())] + (x: T, m1: M1, m2: M2, m3: M3) -> auto; +``` + +With the alternative, the type of the receiver would be constrained, and the +deduced types would be written in a different order: + +> **Alternative:** +> +> ```carbon +> // `m1`, `m2`, and `m3` are methods on class `T`. +> fn Call3MethodsAlternative1 +> [M1:! type, M2:! type, M3:! type, +> T:! ValueBind(M1) & ValueBind(M2) & ValueBind(M3) +> where .(ValueBind(M1).Result) impls Call(()) +> and .(ValueBind(M2).Result) impls Call(()) +> and .(ValueBind(M3).Result) impls Call(())] +> (x: T, m1: M1, m2: M2, m3: M3) -> auto; +> ``` + +Or, the constraints can be moved to the method types at the cost of additional +length: + +> **Alternative:** +> +> ```carbon +> // `m1`, `m2`, and `m3` are methods on class `T`. +> fn Call3MethodsAlternative2 +> [T:! type, +> M1:! type where T impls (ValueBind(.Self) where .Result impls Call(())), +> M2:! type where T impls (ValueBind(.Self) where .Result impls Call(())), +> M3:! type where T impls (ValueBind(.Self) where .Result impls Call(()))] +> (x: T, m1: M1, m2: M2, m3: M3) -> auto; +> ``` From 56337f09ab659eee43c0246b982f1de66cf1b897 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 27 Feb 2024 22:00:38 +0000 Subject: [PATCH 08/77] Checkpoint progress. --- proposals/p3720.md | 63 ++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index da3e9b6b00740..80302d9d77bbc 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -74,8 +74,9 @@ this problem and your approach to solving it? The currently accepted proposals for functions don't support all of the different function signatures for the `Call` interface. For example, it does not support `addr self` or explicit compile-time parameters. That is out of scope of -this proposal, and will be addressed separately. The difference between -functions and methods, however, is in scope. +this proposal, and will be addressed separately, and means that `addr self` +methods won't be considered here. The difference between functions and methods, +however, is in scope. ## Proposal @@ -128,15 +129,14 @@ class Extending { ## Details -TODO: Fully explain the details of the proposed solution. +FIXME: Fully explain the details of the proposed solution. To use instance members of a class, we need to go through the additional step of _binding_. Consider a class `C`: ```carbon class C { - fn Value[self: Self]() -> i32 { return self.x + 5; } - fn Mutate[addr self: Self*]() -> i32 { self.x += 1; return self.x; } + fn F[self: Self]() -> i32 { return self.x + 5; } fn Static() -> i32 { return 2; } var x: i32; } @@ -149,16 +149,16 @@ for each member function (either static class function or method), though, that the type of binding that member with either a `C` value or variable. ```carbon -class __TypeOf_C_Value {} -let __C_Value:! __TypeOf_C_Value = {}; -class __ValueBind_C_Value { +class __TypeOf_C_F {} +let __C_F:! __TypeOf_C_F = {}; +class __ValueBind_C_F { adapt C; } -class __RefBind_C_Value { +class __RefBind_C_F { adapt C; } -// and similarly for Mutate and Static +// and similarly for Static ``` These are the types that result from @@ -167,28 +167,25 @@ an instance of `C` with these member names. For example, ```carbon let v: C = {.x = 3}; -Assert(v.Value() == 8); +Assert(v.F() == 8); var r: C = {.x = 4}; -Assert(r.Value() == 9); -Assert(r.Mutate() == 6); +Assert(r.F() == 9); ``` is interpreted as: ```carbon let v: C = {.x = 3}; -Assert((v as __ValueBind_C_Value).(Call(()).Op)() == 8); +Assert((v as __ValueBind_C_F).(Call(()).Op)() == 8); var r: C = {.x = 4}; -Assert((r as __RefBind_C_Value).(『mutable call()』.Op)() == 9); -Assert((r as __RefBind_C_Mutate).(『mutable call()』.Op)() == 9); +Assert((r as __RefBind_C_F).(Call(()).Op)() == 9); ``` How does this arise? 1. First the simple member access is resolved using the type of the receiver: \ - `v.Value` -> `v.(C.Value)`, `r.Value` -> `r.(C.Value)`, `r.Mutate` -> `r.(C.Mutate)`. - \ - Note that `C.Value` is `__C_Value` with type `__TypeOf_C_Value`. + `v.F` -> `v.(C.F)`, `r.F` -> `r.(C.F)`. \ + Note that `C.F` is `__C_F` with type `__TypeOf_C_F`. 2. If the left of the `.` is a value expression, the value binding operator is applied. Otherwise if it is a reference expression, the reference binding operator is applied. @@ -218,21 +215,21 @@ consistent with other operator interfaces, but I'm using a different name in this document for now to avoid confusion with the `Op` method of the call interfaces. -These binding operations are how we get from `__C_Value` with type -`__TypeOf_C_Value` to `v as __ValueBind_C_Value` or `r as __RefBind_C_Value`: +These binding operations are how we get from `__C_F` with type `__TypeOf_C_F` to +`v as __ValueBind_C_F` or `r as __RefBind_C_F`: ```carbon -impl __TypeOf_C_Value as ValueBind(C) - where .Result = __ValueBind_C_Value { - fn Op[self: Self](x: C) -> __ValueBind_C_Value { - return x as __ValueBind_C_Value; +impl __TypeOf_C_F as ValueBind(C) + where .Result = __ValueBind_C_F { + fn Op[self: Self](x: C) -> __ValueBind_C_F { + return x as __ValueBind_C_F; } } -impl __TypeOf_C_Value as RefBind(C) - where .Result = __RefBind_C_Value { - fn Op[self: Self](p: C*) -> __RefBind_C_Value* { - return p as __RefBind_C_Value*; +impl __TypeOf_C_F as RefBind(C) + where .Result = __RefBind_C_F { + fn Op[self: Self](p: C*) -> __RefBind_C_F* { + return p as __RefBind_C_F*; } } ``` @@ -241,12 +238,12 @@ The last ingredient is the implementation of the call interfaces for these bound types. ```carbon -// Since `C.Value` takes `self: Self` it can be used with both +// Since `C.F` takes `self: Self` it can be used with both // value and reference expressions: -impl __ValueBind_C_Value as Call(()) with .Result = i32; -impl __RefBind_C_Value as Call(()) with .Result = i32; +impl __ValueBind_C_F as Call(()) with .Result = i32; +impl __RefBind_C_F as Call(()) with .Result = i32; -// `C.Static` works the same as `C.Value`, except it also +// `C.Static` works the same as `C.F`, except it also // implements the call interfaces on `__TypeOf_C_Static`. // This allows `v.Static()` and `r.Static()` to work, in // addition to `C.Static()`. From c31a8f2016e773d3bba36195e1885bc4f2d9210e Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 28 Feb 2024 00:23:55 +0000 Subject: [PATCH 09/77] Checkpoint progress. --- proposals/p3720.md | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 80302d9d77bbc..dd653dd967159 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -210,11 +210,6 @@ interface RefBind(T:! type) { } ``` -The name `Bind` here is provisional -- it probably should actually be `Op` to be -consistent with other operator interfaces, but I'm using a different name in -this document for now to avoid confusion with the `Op` method of the call -interfaces. - These binding operations are how we get from `__C_F` with type `__TypeOf_C_F` to `v as __ValueBind_C_F` or `r as __RefBind_C_F`: @@ -413,25 +408,21 @@ implements `Call(())`: ```carbon // `m` can be any method object that implements `Call(())` once bound. -fn CallValueMethod +fn CallMethod [T:! type, M:! ValueBind(T) where .Result impls Call(())] (x: T, m: M) -> auto { return x.(m)(); } ``` -This will work with any value method or static class function. - -**FIXME: Left off here.** - -This implementation of `CallMutatingMethod` will work with inheritance and -virtual methods, using +This will work with any value method or static class function. This will also +work with inheritance and virtual methods, using [the support for implicit conversions of self](#inheritance-and-other-implicit-conversions). ```carbon base class X { virtual fn V[self: Self]() -> i32 { return 1; } - fn B[addr self: Self*](); + fn B[self: Self]() -> i32 { return 0; } } class Y { extend base: X; @@ -445,14 +436,14 @@ class Z { var (x: X, y: Y, z: Z); // Respects inheritance -CallMutatingMethod(&x, X.B); -CallMutatingMethod(&y, X.B); -CallMutatingMethod(&z, X.B); +Assert(CallMethod(x, X.B) == 0); +Assert(CallMethod(y, X.B) == 0); +Assert(CallMethod(z, X.B) == 0); // Respects method overriding -Assert(CallMutatingMethod(&x, X.V) == 1); -Assert(CallMutatingMethod(&y, X.V) == 2); -Assert(CallMutatingMethod(&z, X.V) == 3); +Assert(CallMethod(x, X.V) == 1); +Assert(CallMethod(y, X.V) == 2); +Assert(CallMethod(z, X.V) == 3); ``` **Fields** @@ -490,18 +481,18 @@ Assert(GetField(c, C.n) == 12); In [the generic type of member section](#generic-type-of-a-class-member), the names of members, such as `D.K`, `X.B`, `X.V`, and `C.n`, refer to zero-sized / stateless objects where all the offset information is encoded in the type. -However, the definitions of `CallMutatingMethod`, `CallValueMethod`, `SetField`, -and `GetField` do not depend on that fact and will be usable with objects, such -as C++ pointers-to-members, that include the offset information in the runtime -object state. So we can define binding implementations for them so that they may -be used with Carbon's `.(`**`)` and `->(`**`)` operators. +However, the definitions of `CallMethod`, `SetField`, and `GetField` do not +depend on that fact and will be usable with objects, such as C++ +pointers-to-members, that include the offset information in the runtime object +state. So we can define binding implementations for them so that they may be +used with Carbon's `.(`**`)` and `->(`**`)` operators. For example, this is how I would expect C++ code to call the above Carbon functions: ```cpp struct C { - void F() { ++m; } + int F() const { return m + 1; } int m; }; @@ -514,9 +505,8 @@ int main() { Carbon::SetField(&c, p, 4); assert(c.m == 4); // pointer to method `F` of class C - void (C::* q)() = &C::F; - Carbon::CallMutatingMethod(&c, q); - assert(Carbon::GetField(c, &C::m) == 5); + int (C::*q)() const = &C::F; + assert(Carbon::CallMethod(&c, q) == 5); } ``` From 21339ee4ce7cf693e9773f01597995154bca218a Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 28 Feb 2024 00:32:28 +0000 Subject: [PATCH 10/77] Checkpoint progress. --- proposals/p3720.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index dd653dd967159..8aaac9d1426b8 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -661,7 +661,7 @@ some of the implementations are visible. ## 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), From e4045adf4b13bef4054c11106dbbd49d517a3bbe Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 28 Feb 2024 00:42:30 +0000 Subject: [PATCH 11/77] Checkpoint progress. --- proposals/p3720.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 8aaac9d1426b8..31eda2b248717 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -651,13 +651,13 @@ In this example, the binding of `c` of type `Circle` to `Circle.area` would perform the computation and return the result as an `f64`. If there was some way to customize the result of binding, this could be extended -to support other kinds of properties, such as mutable properties that have both -`get` and `set` methods. The main obstacle to any support for properties with -binding is how the customization would be done. The most natural way to support -this customization would be to have multiple interfaces. The compiler would try -them in a specified order and use the one it found first. This has the downside -of the possibility of different behavior in a checked generic context where only -some of the implementations are visible. +to support other kinds of properties, such as mutable properties that use `get` +and `set` methods to access and mutate the value. The main obstacle to any +support for properties with binding is how the customization would be done. The +most natural way to support this customization would be to have multiple +interfaces. The compiler would try them in a specified order and use the one it +found first. This has the downside of the possibility of different behavior in a +checked generic context where only some of the implementations are visible. ## Rationale From 98013d8a4d0231551b07a2e7724a01ee787380d4 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 28 Feb 2024 21:26:39 +0000 Subject: [PATCH 12/77] Checkpoint progress. --- proposals/p3720.md | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 31eda2b248717..d2f314c91d31b 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -22,6 +22,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Generic type of a class member](#generic-type-of-a-class-member) - [C++ pointer to member](#c-pointer-to-member) - [Member forwarding](#member-forwarding) + - [Future: Tuple indexing](#future-tuple-indexing) - [Future: properties](#future-properties) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) @@ -33,8 +34,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 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` of other classes, for forwarding -use cases. +interfaces. Also allow classes to `extend api` other classes, for forwarding use +cases. ## Problem @@ -63,20 +64,30 @@ separately from the instance of `C` to bind with it? ## Background -FIXME: Is there any background that readers should consider to fully understand -this problem and your approach to solving it? +Member access has been specified in two proposals: -- [Qualified names and member access](/docs/design/expressions/member_access.md) - [Proposal #989: Member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) - [Proposal #2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) -- [Proposal #2875: Functions, function types, and function calls](https://github.com/carbon-language/carbon-lang/pull/2875) -The currently accepted proposals for functions don't support all of the -different function signatures for the `Call` interface. For example, it does not -support `addr self` or explicit compile-time parameters. That is out of scope of -this proposal, and will be addressed separately, and means that `addr self` -methods won't be considered here. The difference between functions and methods, -however, is in scope. +The results of these proposals is recorded in the +["qualified names and member access" design document](/docs/design/expressions/member_access.md). +Notably, there is the process of +[instance binding](/docs/design/expressions/member_access.md#instance-binding), +that can convert a method into a bound method. This is described as an +uncustomizable process, with the members of classes being non-first-class names. + +With +[proposal #3646: Tuples and tuple indexing](https://github.com/carbon-language/carbon-lang/pull/3646), +tuple indexing also uses the member-access syntax, except with numeric names for +the fields. + +The currently accepted proposals for functions, most notably +[Proposal #2875: Functions, function types, and function calls](https://github.com/carbon-language/carbon-lang/pull/2875), +don't support all of the different function signatures for the `Call` interface. +For example, it does not support `addr self` or explicit compile-time +parameters. That is out of scope of this proposal, and will be addressed +separately, and means that `addr self` methods won't be considered here. The +difference between functions and methods, however, is in scope. ## Proposal @@ -626,6 +637,13 @@ 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*`). +### Future: Tuple indexing + +We can reframe the use of the compound member access syntax for tuple fields as +an implementation of binding of tuples with compile-time integer expressions. +The specifics of how this works will be resolved later, once we address how +compile-time interacts with interfaces. + ### Future: properties If there was a way to implement the binding operator to only produce values, From 3fcaebf2485ef3565e239ffe66ea2fb80babbec4 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 28 Feb 2024 21:39:14 +0000 Subject: [PATCH 13/77] Rationale --- proposals/p3720.md | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index d2f314c91d31b..20e401017e43c 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -22,7 +22,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Generic type of a class member](#generic-type-of-a-class-member) - [C++ pointer to member](#c-pointer-to-member) - [Member forwarding](#member-forwarding) - - [Future: Tuple indexing](#future-tuple-indexing) + - [Future: tuple indexing](#future-tuple-indexing) - [Future: properties](#future-properties) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) @@ -637,7 +637,7 @@ 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*`). -### Future: Tuple indexing +### Future: tuple indexing We can reframe the use of the compound member access syntax for tuple fields as an implementation of binding of tuples with compile-time integer expressions. @@ -679,22 +679,28 @@ checked generic context where only some of the implementations are visible. ## 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 is about: + +- Orthogonality: separating the binding process as a separate and independent + step of using the members of a type. +- Being consistent with our overall strategy for defining operators in terms + of interface implementations. +- Allows binding-related functionality to be define through + [library APIs](/docs/project/principles/library_apis_only.md). +- Increases uniformity by making member names into ordinary values with types. +- Adds expressiveness, enabling member forwarding, passing a member as an + argument, and other use cases. + +These benefits advance Carbon's goals including: + +- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem): + by making it easier to reason about more Carbon entities within Carbon + itself, and reducing the number of different concepts that have to be + modeled. +- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write): + through increased consistency, uniformity, and expressiveness. +- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code): + by adding supporti for pointer-to-member constructs. ## Alternatives considered From ca8a9e8120cbe70913d135a6f8d7f8057c617a3b Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 28 Feb 2024 21:55:16 +0000 Subject: [PATCH 14/77] Proposal section --- proposals/p3720.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 20e401017e43c..e7970aac8b615 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -91,8 +91,10 @@ difference between functions and methods, however, is in scope. ## Proposal -FIXME: Briefly and at a high level, how do you propose to solve the problem? Why -will that in fact solve it? +We propose that Carbon defines the compound member access operator, specifically +`x.(y)`, to be defined as in terms of rewrites to invoking an interface method, +like other operators. There are two different interfaces used, depending on +whether `x` is a value expression or a reference expression: ```carbon // For value `x` with type `T` and `y` of type `U`, @@ -110,7 +112,14 @@ interface RefBind(T:! type) { } ``` -`extend api T`: +The other member access operators -- `x.y`, `x->y`, and `x->(y)` -- are defined +by how they rewrite into the `x.(y)` form using these two rules: + +- `x.y` is interpreted as `x.(T.y)` for non-type values `x` with type `T`, and +- `x->y` is interpreted as `(*x).y`. + +In addition, we propose adding a declaration in a class body to find names +defined in another class, `extend api T`: ```carbon class Extended { @@ -138,6 +147,8 @@ class Extending { } ``` +This is used for [member forwarding use cases](#member-forwarding). + ## Details FIXME: Fully explain the details of the proposed solution. @@ -179,8 +190,10 @@ an instance of `C` with these member names. For example, ```carbon let v: C = {.x = 3}; Assert(v.F() == 8); +Assert(v.Static() == 2); var r: C = {.x = 4}; Assert(r.F() == 9); +Assert(r.Static() == 2); ``` is interpreted as: @@ -188,15 +201,19 @@ is interpreted as: ```carbon let v: C = {.x = 3}; Assert((v as __ValueBind_C_F).(Call(()).Op)() == 8); +Assert((v as __ValueBind_C_Static).(Call(()).Op)() == 2); var r: C = {.x = 4}; Assert((r as __RefBind_C_F).(Call(()).Op)() == 9); +Assert((r as __RefBind_C_Static).(Call(()).Op)() == 2); ``` How does this arise? 1. First the simple member access is resolved using the type of the receiver: \ - `v.F` -> `v.(C.F)`, `r.F` -> `r.(C.F)`. \ - Note that `C.F` is `__C_F` with type `__TypeOf_C_F`. + `v.F` -> `v.(C.F)`, `v.Static` -> `v.(C.Static)`, `r.F` -> `r.(C.F)`, `r.Static` + -> `r.(C.Static)`. \ + Note that `C.F` is `__C_F` with type `__TypeOf_C_F`, and `C.Static` is + `__C_Static` with type `__TypeOf_C_Static`. 2. If the left of the `.` is a value expression, the value binding operator is applied. Otherwise if it is a reference expression, the reference binding operator is applied. From e3b86f0de0eeb0f112c1fa54f7a499fb086a2e52 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 28 Feb 2024 21:58:19 +0000 Subject: [PATCH 15/77] Checkpoint progress. --- proposals/p3720.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index e7970aac8b615..07706b8ed59ad 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -115,7 +115,11 @@ interface RefBind(T:! type) { The other member access operators -- `x.y`, `x->y`, and `x->(y)` -- are defined by how they rewrite into the `x.(y)` form using these two rules: -- `x.y` is interpreted as `x.(T.y)` for non-type values `x` with type `T`, and +- `x.y` is interpreted using the existing + [member resolution rules](/docs/design/expressions/member_access.md#member-resolution). + For example, `x.y` is treated as `x.(T.y)` for non-type values `x` with type + `T`. These rules are expanded to support `extend api T` declarations + described next. - `x->y` is interpreted as `(*x).y`. In addition, we propose adding a declaration in a class body to find names From fabe98a9b5ad0019808e27efbcb09706e183deaa Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 29 Feb 2024 00:12:45 +0000 Subject: [PATCH 16/77] Checkpoint progress. --- proposals/p3720.md | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 07706b8ed59ad..4485b879ae717 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -27,6 +27,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) - [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) @@ -62,6 +63,9 @@ They may be used to access the members of `x` using Carbon's `x.(C.M)` or `x.(C.f)`. What is their type? Can they be passed to a function separately from the instance of `C` to bind with it? +The other issue is how we clearly delineate the `self` associated with a method +signature as separate from the `self` of function values. + ## Background Member access has been specified in two proposals: @@ -155,8 +159,6 @@ This is used for [member forwarding use cases](#member-forwarding). ## Details -FIXME: Fully explain the details of the proposed solution. - To use instance members of a class, we need to go through the additional step of _binding_. Consider a class `C`: @@ -189,7 +191,9 @@ class __RefBind_C_F { These are the types that result from [instance binding](/docs/design/expressions/member_access.md#instance-binding) -an instance of `C` with these member names. For example, +an instance of `C` with these member names. They define the bound method value +and bound method type of [proposal #2875](/proposals/p2875.md#bound-methods). +For example, ```carbon let v: C = {.x = 3}; @@ -798,3 +802,38 @@ length: > M3:! type where T impls (ValueBind(.Self) where .Result impls Call(()))] > (x: T, m1: M1, m2: M2, m3: M3) -> auto; > ``` + +### Reference binding produces a value that wraps a pointer + +Consider a mutating method on a class: + +```carbon +class Counter { + var count: i32 = 0; + fn Increment[addr self: Self*]() { + self->count += 1; + } +} + +var c: Counter = {}; +``` + +This proposal says `c.Increment` is a reference expression with a type that +adapts `Counter`. For `c.Increment()` to affect the value of `c.count`, there +needs to be some way for the `Call` operator to mutate `c`. The current +definition of `Call` takes `self` by value, though, so this doesn't work. +Addressing this is out of scope of the current proposal. + +We could instead make `c.Increment` be a value holding `&c`. That would allow +`Call` to work even when taking `self` by value. This is the solution likely +implied by the current [proposal #2875](/proposals/p2875.md#bound-methods), +though that proposal does not say what the bound method type is at all. It +leaves two other problems, however: + +- We will still need a way to support function objects that are mutated by + calling them. This comes up, for example, with C++ types that define + `operator()`. +- We want the proposed behavior when binding a [field](#data-fields). + +As a result, it would be better to evaluate this alternative later as part of +considering mutation in calls. From fa29cc919aacda52f50b1a076c9edd841ee13c2a Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 29 Feb 2024 00:29:47 +0000 Subject: [PATCH 17/77] Checkpoint progress. --- proposals/p3720.md | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 4485b879ae717..eb033a1be32c9 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -20,6 +20,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Inheritance and other implicit conversions](#inheritance-and-other-implicit-conversions) - [Data fields](#data-fields) - [Generic type of a class member](#generic-type-of-a-class-member) + - [Methods](#methods) + - [Fields](#fields) - [C++ pointer to member](#c-pointer-to-member) - [Member forwarding](#member-forwarding) - [Future: tuple indexing](#future-tuple-indexing) @@ -225,8 +227,7 @@ How does this arise? 2. If the left of the `.` is a value expression, the value binding operator is applied. Otherwise if it is a reference expression, the reference binding operator is applied. -3. The result of the binding has a type that implements one or both of the call - interfaces. +3. The result of the binding has a type that implements the call interface. The binding operators are defined using two dedicated interfaces: @@ -276,8 +277,8 @@ impl __RefBind_C_F as Call(()) with .Result = i32; // `C.Static` works the same as `C.F`, except it also // implements the call interfaces on `__TypeOf_C_Static`. -// This allows `v.Static()` and `r.Static()` to work, in -// addition to `C.Static()`. +// This allows `C.Static()` to work, in addition to +// `v.Static()` and `r.Static()`. impl __ValueBind_C_Static as Call(()) with .Result = i32; impl __RefBind_C_Static as Call(()) with .Result = i32; impl __TypeOf_C_Static as Call(()) where .Result = i32; @@ -433,9 +434,9 @@ x.m == x.(C.m) ### Generic type of a class member Given the above, we can now write a constraint on a symbolic parameter to match -the names of an unbound class member. There are a couple of different cases: +the names of an unbound class member. There are a two cases: methods and fields. -**Methods** +#### Methods Restricting to value methods, since mutating (`addr self`) methods are out of scope for this proposal, the receiver object may be passed by value. To be able @@ -447,6 +448,8 @@ implements `Call(())`: fn CallMethod [T:! type, M:! ValueBind(T) where .Result impls Call(())] (x: T, m: M) -> auto { + // `x.(m)` is rewritten to a call to `ValueBind(T).Op`. The + // constraint on `M` ensures the result implements `Call(())`. return x.(m)(); } ``` @@ -482,23 +485,32 @@ Assert(CallMethod(y, X.V) == 2); Assert(CallMethod(z, X.V) == 3); ``` -**Fields** +#### Fields Fields can be accessed, given the type of the field ```carbon -fn SetField - [T:! type, F:! RefBind(T) where .Result = i32] - (x: T*, f: F, y: i32) { - x->(f) = y; -} - fn GetField [T:! type, F:! ValueBind(T) where .Result = i32] (x: T, f: F) -> i32 { + // `x.(f)` is rewritten to `f.((F as ValueBind(T)).Op)(x)`, + // and `(F as ValueBind(T)).Op` is a method on `f` with + // return type `i32` by the constraint on `F`. return x.(f); } +fn SetField + [T:! type, F:! RefBind(T) where .Result = i32] + (x: T*, f: F, y: i32) { + // `x->(f)` is rewritten to `(*x).(f)`, which then + // becomes: `*f.((F as RefBind(T)).Op)(&*x)` + // The constraint `F` says the return type of + // `(F as RefBind(T)).Op` is `i32*`, which is + // dereferenced to get an `i32` reference expression + // which may then be assigned. + x->(f) = y; +} + class C { var m: i32; var n: i32; From 42e870abaed24a6ad4dfd3b3543b8eefa9023074 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 29 Feb 2024 00:35:08 +0000 Subject: [PATCH 18/77] Checkpoint progress. --- proposals/p3720.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index eb033a1be32c9..f7be71f2b7766 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -618,18 +618,20 @@ 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)`. -**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. - -**Note:** This mechanism has some potential uses beyond member forwarding. For -example, a class could extend the api of a class it implicitly converts to, see -[inheritance and other implicit conversions](#inheritance-and-other-implicit-conversions). -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. +> **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. + +> **Note:** This mechanism has some potential uses beyond member forwarding. For +> example, a class could extend the API of a class it implicitly converts to, +> see +> [inheritance and other implicit conversions](#inheritance-and-other-implicit-conversions). +> 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. For the second ingredient, the binding interfaces already provide everything needed, we just need to make a parameterized implementation of them: From 60ecc4695843d6832fd3bdfea12d006afd626745 Mon Sep 17 00:00:00 2001 From: josh11b Date: Mon, 4 Mar 2024 14:48:16 -0800 Subject: [PATCH 19/77] Apply suggestions from code review Co-authored-by: Jon Ross-Perkins --- proposals/p3720.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index f7be71f2b7766..bd0b4539db0b7 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -142,7 +142,7 @@ class Extending { extend api Extended; } -var e: Extended +var e: Extending; ``` This means that lookup into `Extending` also looks in the type `Extended` for @@ -610,7 +610,7 @@ by leads issues ```carbon class Box(T:! type) { - var : T*; + var ptr: T*; extend api T; } ``` @@ -739,7 +739,7 @@ These benefits advance Carbon's goals including: - [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write): through increased consistency, uniformity, and expressiveness. - [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code): - by adding supporti for pointer-to-member constructs. + by adding support for pointer-to-member constructs. ## Alternatives considered From 37e967ff53e89e345eecb49ec79c6dfbe18a3c54 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 5 Mar 2024 22:46:44 +0000 Subject: [PATCH 20/77] Updates based on comments --- proposals/p3720.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index bd0b4539db0b7..4b1fb7656791e 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -140,20 +140,26 @@ class Extended { 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)`. `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: +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); } ``` @@ -676,6 +682,9 @@ 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*`). +> **Future:** We might want `extend api` to also get (some) interface +> implementations, like `extend base` does. + ### Future: tuple indexing We can reframe the use of the compound member access syntax for tuple fields as From 57d020dac02e0921c257aea828cc029dc1ea6dec Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Mar 2024 21:59:11 +0000 Subject: [PATCH 21/77] Add section on C++ operator overloading --- proposals/p3720.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index 4b1fb7656791e..6e100b85db208 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -24,6 +24,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Fields](#fields) - [C++ pointer to member](#c-pointer-to-member) - [Member forwarding](#member-forwarding) + - [C++ operator overloading](#c-operator-overloading) - [Future: tuple indexing](#future-tuple-indexing) - [Future: properties](#future-properties) - [Rationale](#rationale) @@ -685,6 +686,27 @@ With these two ingredients, `b.Search(32)` is equivalent to > **Future:** We might want `extend api` to also get (some) interface > implementations, like `extend base` does. +### 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. + +Carbon instead will only have a single interface for customizing dereference, +corresponding to `operator*` not `operator->`. All uses of `x->y` will be +rewritten to use `(*x).y` instead. This may cause some friction when porting C++ +code where those operators are not consistent. If the C++ code is just missing +the definition of `operator*` corresponding to an `operator->`, a workaround +would be just to define `operator*`. + +Other cases of divergence between those operators should be rare, since that is +both surprising to users and for the common case of iterators, violate the C++ +requirements. If necessary, we can in the future introduce a specific construct +just for C++ interop that invokes the C++ arrow operator, such as +`CppArrowOperator(x)`, that returns a pointer. + ### Future: tuple indexing We can reframe the use of the compound member access syntax for tuple fields as From 3c31b8f8e21fc15936427892e912fbd615eb653d Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Mar 2024 23:20:28 +0000 Subject: [PATCH 22/77] Rules for shadowing and incomplete types --- proposals/p3720.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 6e100b85db208..41ee4f4b4456d 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -623,7 +623,10 @@ 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)`. +this way, `b.Search` will find `b.(String.Search)`. The general rule 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. The extended type +must be complete before the first time name lookup into it is performed. > **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 From 88f1eb64e509da8d4d5dc1f38624006f43110472 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Mar 2024 23:23:55 +0000 Subject: [PATCH 23/77] Link to context --- proposals/p3720.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index 41ee4f4b4456d..4c1712e8ab3fd 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -710,6 +710,11 @@ requirements. If necessary, we can in the future introduce a specific construct just for C++ interop that invokes the C++ arrow operator, such as `CppArrowOperator(x)`, that returns a pointer. +**Context:** This was discuseed in +[2024-02-29 open discussion](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.5vj8ohrvqjqh) +and in +[a comment on this proposal](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1507917882). + ### Future: tuple indexing We can reframe the use of the compound member access syntax for tuple fields as From 4339ae7fc1941ae1299c0deb2b82f443e26163b5 Mon Sep 17 00:00:00 2001 From: josh11b Date: Wed, 6 Mar 2024 15:27:26 -0800 Subject: [PATCH 24/77] Apply suggestions from code review Co-authored-by: Richard Smith --- proposals/p3720.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 4c1712e8ab3fd..0d2daf26f9a52 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -99,7 +99,7 @@ difference between functions and methods, however, is in scope. ## Proposal We propose that Carbon defines the compound member access operator, specifically -`x.(y)`, to be defined as in terms of rewrites to invoking an interface method, +`x.(y)`, to be defined in terms of rewrites to invoking an interface method, like other operators. There are two different interfaces used, depending on whether `x` is a value expression or a reference expression: @@ -336,7 +336,7 @@ impl [T:! type where .Self* impls ImplicitAs(B*)] } ``` -This matches the expected semantics method calls, even for methods of final +This matches the expected semantics of method calls, even for methods of final classes. Note that the implementation of the bind interfaces is where the `Self` type of @@ -403,7 +403,7 @@ let __C_m:! __TypeOf_C_m = {}; impl __TypeOf_C_m as ValueBind(C) where .Result = i32 { fn Op[self: Self](x: C) -> i32 { // Effectively performs `x.m`, but without triggering binding again. - return value_compiler_intrinsic(v, __OffsetOf_C_m, i32) + return value_compiler_intrinsic(x, __OffsetOf_C_m, i32) } } @@ -420,11 +420,11 @@ These definitions give us the desired semantics: ```carbon // For value `v` with type `T` and `y` of type `U`, -// `v.(y)` is `y.(U as ValueBind(T)).Op(v)` +// `v.(y)` is `y.((U as ValueBind(T)).Op)(v)` v.m == v.(C.m) == v.(__C_m) == v.(__C_m as (__TypeOf_C_m as ValueBind(C))) - == __C_m.(__TypeOf_C_m as ValueBind(C)).Op(v) + == __C_m.((__TypeOf_C_m as ValueBind(C)).Op)(v) == value_compiler_intrinsic(v, __OffsetOf_C_m, i32) // For reference expression `var x: T` and `y` of type `U`, @@ -598,7 +598,7 @@ There are two ingredients to make this work: 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). +[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)): From 2d6105424343ba66d459bda8855ac719a99948ac Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 7 Mar 2024 00:48:39 +0000 Subject: [PATCH 25/77] Checkpoint progress. --- proposals/p3720.md | 110 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index 0d2daf26f9a52..d2159a710e1ac 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -23,6 +23,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Methods](#methods) - [Fields](#fields) - [C++ pointer to member](#c-pointer-to-member) + - [Interface members](#interface-members) + - [Future work: non-instance interface members](#future-work-non-instance-interface-members) - [Member forwarding](#member-forwarding) - [C++ operator overloading](#c-operator-overloading) - [Future: tuple indexing](#future-tuple-indexing) @@ -565,6 +567,114 @@ int main() { } ``` +### Interface members + +Instance members of an interface, such as methods, can use this framework. For +example, given these declarations: + +```carbon +interface I { + fn F[self: Self](); +} +class C { + impl as I; +} +let c: C = {}; +``` + +Then `I.F` is its own value with its own type: + +```carbon +class __TypeOf_I_F {} +let __I_F: __TypeOf_I_F = {}; +``` + +That type implements `ValueBind` for any type that implements the interface `I`: + +```carbon +class __ValueBind_I_F(T:! I) { + adapt T; +} +impl forall [T:! I] __TypeOf_I_F as ValueBind(T) + where .Result = __ValueBind_I_F(T) { + fn Op[self: Self](x: T) -> __ValueBind_I_F(T) { + // Valid since `__ValueBind_I_F(T)` adapts `T`: + return x as __ValueBind_I_F(T); + } +} +``` + +The actual dispatch to the `I.F` method of `C` happens in the implementation of +the `Call` interface of this adapter type that is the result of value binding. +So, this implementation of `C as I`: + +```carbon +impl C as I { + fn F[self: Self]() { + Fanfare(self); + } +} +``` + +Results in this implementation: + +```carbon +impl __ValueBind_I_F(C) as Call(()) where .Result = () { + fn Op[self: Self]() { + let __self: C = self as C; + Fanfare(__self); + } +} +``` + +A call such as `c.(I.F)()` goes through these rewrites: + +```carbon +c.(I.F)() == c.(__I_F)() + == __I_F.((__TypeOf_I_F as ValueBind(C)).Op)(c)() + == (c as __ValueBind_I_F(C))() + == ((c as __ValueBind_I_F(C)) as Call(())).Op() + == Fanfare(c) +``` + +#### Future work: non-instance interface members + +For now, we use the existing built-in, non-extensible, non-first-class mechanism +for non-instance members of interfaces, such as: + +```carbon +interface J { + fn G(); +} +impl C as J; +``` + +The issue is we don't have a good thing to put in for the question marks in the +implementation of `ValueBind`: + +```carbon +impl __TypeOf_I_G as ValueBind(???) where .Result = ???; +``` + +To find this implementation, the argument to `ValueBind` needs to match the type +of the value to the left of the `.`. Given a symbolic value like `T:! J`, +`T.(J.G)` would look for `ValueBind(I)`. However, looking in a concrete type, as +in `C.(J.G)` would look for `ValueBind(type)`. Further, other symbolic values +would look in other places, such as `U:! I & J`, `U.(J.G)` would look for +`ValueBind(I & J)`. In none of these cases would we have a good way to determine +the result type. + +To fix this, we would need a way to dispatch on the actual value of the type. To +invent a syntax, the implementation could be declared something like: + +```carbon +impl forall [T:! J] __TypeOf_I_G as ValueBind(type where .Self = T) + where .Result = __ValueBind_I_G(T); +``` + +In addition, the actual lookup procedure would need to be modified in some way +so that this implementation would be found in all the desired situations. + ### Member forwarding Consider a class that we want to act like it has the members of another type. From d81c7fa45d4bc8af3601df81bceb934d03c7e41b Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 7 Mar 2024 01:03:49 +0000 Subject: [PATCH 26/77] Ambiguity when extending multiple times --- proposals/p3720.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index d2159a710e1ac..d65095e026b68 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -736,7 +736,9 @@ 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 general rule 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. The extended type -must be complete before the first time name lookup into it is performed. +must be complete before the first time name lookup into it 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. > **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 From 10c95c4bbe5947b37e80dc1219d9100fb4012dbd Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 7 Mar 2024 01:09:50 +0000 Subject: [PATCH 27/77] Text about member access error --- proposals/p3720.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index d65095e026b68..4dd73f8834616 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -238,6 +238,21 @@ How does this arise? operator is applied. 3. The result of the binding has a type that implements the call interface. +> **Note:** The current wording in +> [member_access.md](/docs/design/expressions/member_access.md) says that +> `v.(C.Static)` and `r.(C.Static)` are both invalid, because they don't perform +> member name lookup, instance binding, nor impl lookup -- the `v.` and `r.` +> portions are redundant. That rule is now a best-effort diagnostic and should +> not trigger as the result of rewrites performed by the compiler or when the +> problem is not contained in a single line. For example, it would be reasonable +> for the compiler to allow: +> +> ```carbon +> let M:! auto = C.Static +> v.(M)(); +> r.(M)(); +> ``` + The binding operators are defined using two dedicated interfaces: ```carbon From af17d5483a6aa7d3ef96f44132959edf65a558bd Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 7 Mar 2024 21:36:06 +0000 Subject: [PATCH 28/77] Note about overloading --- proposals/p3720.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 4dd73f8834616..badc168bd95b1 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -244,14 +244,20 @@ How does this arise? > member name lookup, instance binding, nor impl lookup -- the `v.` and `r.` > portions are redundant. That rule is now a best-effort diagnostic and should > not trigger as the result of rewrites performed by the compiler or when the -> problem is not contained in a single line. For example, it would be reasonable -> for the compiler to allow: +> problem is not contained in a single expression. For example, it would be +> reasonable for the compiler to allow: > > ```carbon > let M:! auto = C.Static > v.(M)(); > r.(M)(); > ``` +> +> Note that with overloaded names, the `M` might be an instance member in some +> cases and a non-instance member in others, depending on the arguments passed. +> Since the call happens after binding, this gives another reason to consider +> this a best-effort error on a whole expression, rather than a strict rule just +> about binding. The binding operators are defined using two dedicated interfaces: From 1737ee0ded248a24fb8182b37f6fcb2274c77f78 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 7 Mar 2024 21:46:27 +0000 Subject: [PATCH 29/77] Checkpoint progress. --- proposals/p3720.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index badc168bd95b1..fd41061b0fcfb 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -674,7 +674,7 @@ The issue is we don't have a good thing to put in for the question marks in the implementation of `ValueBind`: ```carbon -impl __TypeOf_I_G as ValueBind(???) where .Result = ???; +impl __TypeOf_J_G as ValueBind(???) where .Result = ???; ``` To find this implementation, the argument to `ValueBind` needs to match the type @@ -689,8 +689,8 @@ To fix this, we would need a way to dispatch on the actual value of the type. To invent a syntax, the implementation could be declared something like: ```carbon -impl forall [T:! J] __TypeOf_I_G as ValueBind(type where .Self = T) - where .Result = __ValueBind_I_G(T); +impl forall [T:! J] __TypeOf_J_G as ValueBind(type where .Self = T) + where .Result = __ValueBind_J_G(T); ``` In addition, the actual lookup procedure would need to be modified in some way From 69153495503fcf178a340998c486e33259dc5614 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 8 Mar 2024 01:12:24 +0000 Subject: [PATCH 30/77] Add `TypeBind` to support non-instance interface members --- proposals/p3720.md | 100 +++++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 26 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index fd41061b0fcfb..0fd5c6877c7b3 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -23,8 +23,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Methods](#methods) - [Fields](#fields) - [C++ pointer to member](#c-pointer-to-member) - - [Interface members](#interface-members) - - [Future work: non-instance interface members](#future-work-non-instance-interface-members) + - [Instance interface members](#instance-interface-members) + - [Non-instance interface members](#non-instance-interface-members) - [Member forwarding](#member-forwarding) - [C++ operator overloading](#c-operator-overloading) - [Future: tuple indexing](#future-tuple-indexing) @@ -102,8 +102,8 @@ difference between functions and methods, however, is in scope. We propose that Carbon defines the compound member access operator, specifically `x.(y)`, to be defined in terms of rewrites to invoking an interface method, -like other operators. There are two different interfaces used, depending on -whether `x` is a value expression or a reference expression: +like other operators. There are three different interfaces used, depending on +whether `x` is a value expression, a reference expression, or a facet: ```carbon // For value `x` with type `T` and `y` of type `U`, @@ -119,6 +119,13 @@ interface RefBind(T:! type) { let Result:! type; fn Op[self: Self](p: T*) -> Result*; } + +// For a facet value `T`, `y` of type `U`, +// `T.(y)` is `y.((U as TypeBind(T)).Op)()`. +interface TypeBind(T:! type) { + let Result:! type; + fn Op[self: Self]() -> Result; +} ``` The other member access operators -- `x.y`, `x->y`, and `x->(y)` -- are defined @@ -129,6 +136,8 @@ by how they rewrite into the `x.(y)` form using these two rules: For example, `x.y` is treated as `x.(T.y)` for non-type values `x` with type `T`. These rules are expanded to support `extend api T` declarations described next. + - Simple member access of a facet `T`, as in `T.y`, is not rewritten into + the `T.(`\_\_\_`)` form. - `x->y` is interpreted as `(*x).y`. In addition, we propose adding a declaration in a class body to find names @@ -259,7 +268,7 @@ How does this arise? > this a best-effort error on a whole expression, rather than a strict rule just > about binding. -The binding operators are defined using two dedicated interfaces: +The binding operators are defined using three dedicated interfaces: ```carbon // For value `x` with type `T` and `y` of type `U`, @@ -275,6 +284,13 @@ interface RefBind(T:! type) { let Result:! type; fn Op[self: Self](p: T*) -> Result*; } + +// For a facet value `T`, `y` of type `U`, +// `T.(y)` is `y.((U as TypeBind(T)).Op)()`. +interface TypeBind(T:! type) { + let Result:! type; + fn Op[self: Self]() -> Result; +} ``` These binding operations are how we get from `__C_F` with type `__TypeOf_C_F` to @@ -296,6 +312,9 @@ impl __TypeOf_C_F as RefBind(C) } ``` +> **Note:** `TypeBind` is used for +> [non-instance interface members](#non-instance-interface-members). + The last ingredient is the implementation of the call interfaces for these bound types. @@ -588,7 +607,7 @@ int main() { } ``` -### Interface members +### Instance interface members Instance members of an interface, such as methods, can use this framework. For example, given these declarations: @@ -607,7 +626,7 @@ Then `I.F` is its own value with its own type: ```carbon class __TypeOf_I_F {} -let __I_F: __TypeOf_I_F = {}; +let __I_F:! __TypeOf_I_F = {}; ``` That type implements `ValueBind` for any type that implements the interface `I`: @@ -658,10 +677,10 @@ c.(I.F)() == c.(__I_F)() == Fanfare(c) ``` -#### Future work: non-instance interface members +### Non-instance interface members -For now, we use the existing built-in, non-extensible, non-first-class mechanism -for non-instance members of interfaces, such as: +Non-instance members use the `TypeBind` interface instead. For this example, +`J.G` is a non-instance function: ```carbon interface J { @@ -670,31 +689,60 @@ interface J { impl C as J; ``` -The issue is we don't have a good thing to put in for the question marks in the -implementation of `ValueBind`: +Again the member is given its own type and value: + +```carbon +class __TypeOf_J_G {} +let __J_G:! __TypeOf_J_G = {}; +``` + +Since this is a non-instance member, this type implements `TypeBind` instead of +`ValueBind`: + +```carbon +class __TypeBind_J_G(T:! J) {} +impl forall [T:! J] __TypeOf_J_G as TypeBind(T) + where .Result = __TypeBind_J_G(T) { + fn Op[self: Self]() -> __TypeBind_J_G(T) { + return {}; + } +} +``` + +So, this implementation of `C as J`: ```carbon -impl __TypeOf_J_G as ValueBind(???) where .Result = ???; +impl C as J { + fn G() { + Fireworks(); + } +} ``` -To find this implementation, the argument to `ValueBind` needs to match the type -of the value to the left of the `.`. Given a symbolic value like `T:! J`, -`T.(J.G)` would look for `ValueBind(I)`. However, looking in a concrete type, as -in `C.(J.G)` would look for `ValueBind(type)`. Further, other symbolic values -would look in other places, such as `U:! I & J`, `U.(J.G)` would look for -`ValueBind(I & J)`. In none of these cases would we have a good way to determine -the result type. +Results in this implementation: + +```carbon +impl __TypeBind_J_G(C) as Call(()) where .Result = () { + fn Op[self: Self]() { + Fireworks(); + } +} +``` -To fix this, we would need a way to dispatch on the actual value of the type. To -invent a syntax, the implementation could be declared something like: +A call such as `C.(J.G)()` goes through these rewrites: ```carbon -impl forall [T:! J] __TypeOf_J_G as ValueBind(type where .Self = T) - where .Result = __ValueBind_J_G(T); +C.(J.G)() == C.(__J_G)() + == __J_G.((__TypeOf_J_G as TypeBind(C)).Op)()() + == ({} as __TypeBind_J_G(C))() + == (({} as __TypeBind_J_G(C)) as Call(())).Op() + == Fireworks() ``` -In addition, the actual lookup procedure would need to be modified in some way -so that this implementation would be found in all the desired situations. +> **Note:** Binding for non-instance members doesn't work with `ValueBind`, we +> need `TypeBind`. Otherwise there is no way to get the value `C` into the +> result type. Furthermore, we want `TypeBind` implementation no matter which +> facet of the type is used in the code. ### Member forwarding From 2cd3b494d0888650095722b7e10f86e0e8c17b33 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 8 Mar 2024 01:14:14 +0000 Subject: [PATCH 31/77] Checkpoint progress. --- proposals/p3720.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 0fd5c6877c7b3..28db19d78cd47 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -679,8 +679,8 @@ c.(I.F)() == c.(__I_F)() ### Non-instance interface members -Non-instance members use the `TypeBind` interface instead. For this example, -`J.G` is a non-instance function: +Non-instance members use the `TypeBind` interface instead. For example, if `G` +is a non-instance function of an interface `J`: ```carbon interface J { From b1277b129f8adce10ce472aee37e9ce3f716fa87 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 8 Mar 2024 04:50:58 +0000 Subject: [PATCH 32/77] `CompileBind` alternative --- proposals/p3720.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index 28db19d78cd47..40e5a3722833b 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -33,6 +33,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Alternatives considered](#alternatives-considered) - [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) @@ -1071,3 +1072,19 @@ leaves two other problems, however: As a result, it would be better to evaluate this alternative later as part of considering mutation in calls. + +### Separate interface for compile-time binding instead of type binding + +The first proposed way to handle +[non-instance interface members](#non-instance-interface-members) was in +[the #typesystem channel on Discord on 2024-03-07](https://discord.com/channels/655572317891461132/708431657849585705/1215405895752618134). +The suggestion was to have a `CompileBind` interface used for any compile-time +value to the left of the `.`. It would have access to the value, which is needed +when accessing the members of a type. + +We eventually concluded that the special treatment was specifically needed for +types, not all compile-time values. The +[insight](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.1k8r5fhfwyh6) +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)). From d480f3dfadd6dae15bd75cca79d2b3894ce98e63 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 8 Mar 2024 22:57:30 +0000 Subject: [PATCH 33/77] Other uses of `extend api` --- proposals/p3720.md | 99 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 12 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 40e5a3722833b..22588f83d6b7e 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -26,6 +26,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [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: tuple indexing](#future-tuple-indexing) - [Future: properties](#future-properties) @@ -141,8 +144,8 @@ by how they rewrite into the `x.(y)` form using these two rules: the `T.(`\_\_\_`)` form. - `x->y` is interpreted as `(*x).y`. -In addition, we propose adding a declaration in a class body to find names -defined in another class, `extend api T`: +In addition, we propose adding a declaration in a type definition to find names +defined in another type, `extend api T`: ```carbon class Extended { @@ -775,6 +778,8 @@ 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 [simple member access](/docs/design/expressions/member_access.md) for the class `Box(T)`. Normally simple member access on a value like `b` @@ -816,14 +821,10 @@ is an ambiguity error that needs to be 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. -> **Note:** This mechanism has some potential uses beyond member forwarding. For -> example, a class could extend the API of a class it implicitly converts to, -> see -> [inheritance and other implicit conversions](#inheritance-and-other-implicit-conversions). -> 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. +> **Future:** We might want `extend api` to also get (some) interface +> implementations, like `extend base` does. + +#### 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: @@ -868,8 +869,82 @@ 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*`). -> **Future:** We might want `extend api` to also get (some) interface -> implementations, like `extend base` does. +### Other uses of `extend api` + +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). +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 From ab207b0e546fd1516aa97860257b59cf008cdae0 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 9 Mar 2024 00:45:03 +0000 Subject: [PATCH 34/77] Binding rewrite only once --- proposals/p3720.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 22588f83d6b7e..f27310f17d68c 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -297,8 +297,7 @@ interface TypeBind(T:! type) { } ``` -These binding operations are how we get from `__C_F` with type `__TypeOf_C_F` to -`v as __ValueBind_C_F` or `r as __RefBind_C_F`: +These binding operations are implemented for the types of the class members: ```carbon impl __TypeOf_C_F as ValueBind(C) @@ -319,6 +318,28 @@ impl __TypeOf_C_F as RefBind(C) > **Note:** `TypeBind` is used for > [non-instance interface members](#non-instance-interface-members). +Those implementations are how we get from `__C_F` with type `__TypeOf_C_F` to +`v as __ValueBind_C_F` or `r as __RefBind_C_F`: + +```carbon +// `v` is a value and so uses `ValueBind` +v.F() == v.(C.F)() + == v.(__C_F)() + == __C_F.((__TypeOf_C_F as ValueBind(C)).Op(v))() + == (v as __ValueBind_C_F)() + == (v as __ValueBind_C_F).(Call(()).Op)() + +// `v` is a reference expression and so uses `RefBind` +r.F() == r.(C.F)() + == r.(__C_F)() + == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&v)))() + == (*(&v as __RefBind_C_F*))() + == (*(&v as __RefBind_C_F*)).(Call(()).Op)() +``` + +> **Note:** This rewrite results in a method call, but the rewrite is only +> applied once, not applied again to the resulting method call. + The last ingredient is the implementation of the call interfaces for these bound types. @@ -477,7 +498,7 @@ v.m == v.(C.m) // `x.(y)` is `*y.(U as RefBind(T)).Op(&x)` x.m == x.(C.m) == x.(__C_m) - == *__C_m.(__TypeOf_C_m as RefBind(C)).Op(&x) + == *__C_m.((__TypeOf_C_m as RefBind(C)).Op)(&x) == *offset_compiler_intrinsic(&x, __OffsetOf_C_m, i32) // Note that this requires `x` to be a reference expression, // so `&x` is valid, and produces a reference expression, From 6655d9d0e443099e0b28e9d94166433437d522ee Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 12 Mar 2024 20:29:04 +0000 Subject: [PATCH 35/77] No access control on `extend api` --- proposals/p3720.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index f27310f17d68c..57f52e206cb51 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -145,7 +145,7 @@ by how they rewrite into the `x.(y)` form using these two rules: - `x->y` is interpreted as `(*x).y`. In addition, we propose adding a declaration in a type definition to find names -defined in another type, `extend api T`: +defined in another type, [`extend api T`](#extend-api): ```carbon class Extended { @@ -842,6 +842,9 @@ is an ambiguity error that needs to be 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. + > **Future:** We might want `extend api` to also get (some) interface > implementations, like `extend base` does. From bdf5a74a39263602542e5da547e5c86de6fd8449 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 12 Mar 2024 20:46:35 +0000 Subject: [PATCH 36/77] Members of tuples and structs --- proposals/p3720.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index 57f52e206cb51..4562eab0bddc8 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -505,6 +505,37 @@ x.m == x.(C.m) // since it is the result of dereferencing a pointer. ``` +The fields of [tuple types](/docs/design/tuples.md) and +[struct types](/docs/design/classes.md#struct-types) operate the same way. + +```carbon +let t_let: (i32, i32) = (3, 6); +Assert(t_let.(((i32, i32) as type).0) == 3); + +var t_var: (i32, i32) = (4, 8); +Assert(t_var.(((i32, i32) as type).1) == 8); +t_var.(((i32, i32) as type).1) = 9; +Assert(t_var.1 == 9); + +let s_let: {.x: i32, .y: i32} = {.x = 5, .y = 10}; +Assert(s_let.({.x: i32, .y: i32}.x) == 5); + +var s_var: {.x: i32, .y: i32} = {.x = 6, .y = 12}; +Assert(s_var.({.x: i32, .y: i32}.y) == 12); +s_var.({.x: i32, .y: i32}.y) = 13; +Assert(s_var.y == 13); +``` + +For example, `{.x: i32, .y: i32}.x` is a value `__Struct_x_i32_y_i32_Field_x` of +a type `__TypeOf_Struct_x_i32_y_i32_Field_x` (that is zero-sized / has no state) +that implements the binding interfaces for any type that implicitly converts to +`{.x: i32, .y: i32}`. + +Note that for tuples, the `as type` is needed since `(i32, i32)` on its own is a +tuple, not a type. In particular `(i32, i32)` is not the type of `t_let` or +`t_var`. `(i32, i32).0` is just `i32`, and isn't the name of the first element +of an `(i32, i32)` tuple. + ### Generic type of a class member Given the above, we can now write a constraint on a symbolic parameter to match From 314c2d6974d1ca0bb40771247d69b33d64122626 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 12 Mar 2024 22:02:55 +0000 Subject: [PATCH 37/77] Checkpoint progress. --- proposals/p3720.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 4562eab0bddc8..16106b5c9d9a1 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -526,9 +526,10 @@ s_var.({.x: i32, .y: i32}.y) = 13; Assert(s_var.y == 13); ``` -For example, `{.x: i32, .y: i32}.x` is a value `__Struct_x_i32_y_i32_Field_x` of -a type `__TypeOf_Struct_x_i32_y_i32_Field_x` (that is zero-sized / has no state) -that implements the binding interfaces for any type that implicitly converts to +For example, `{.x: i32, .y: i32}.x` is a value `__Struct_x_i32_y_i32_Field_x`, +analogous to `__C_m`, of a type `__TypeOf_Struct_x_i32_y_i32_Field_x` (that is +zero-sized / has no state), analogous to `__TypeOf_C_m`, that implements the +binding interfaces for any type that implicitly converts to `{.x: i32, .y: i32}`. Note that for tuples, the `as type` is needed since `(i32, i32)` on its own is a From 97004ac6748a4f39e664d7409401df1811adc25c Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 13 Mar 2024 21:16:00 +0000 Subject: [PATCH 38/77] `extend api`: no incomplete types, example with implicit conversion --- proposals/p3720.md | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 16106b5c9d9a1..2350692a8755e 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -37,6 +37,7 @@ 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) @@ -864,9 +865,9 @@ 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 general rule 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. The extended type -must be complete before the first time name lookup into it 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. +must be complete. 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. > **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 @@ -932,6 +933,22 @@ 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. @@ -1219,3 +1236,23 @@ 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). From cbfab862e122511178434bc7f251a04898fcdeeb Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:10:34 -0700 Subject: [PATCH 39/77] Apply suggestions from code review Co-authored-by: Chandler Carruth --- proposals/p3720.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 2350692a8755e..392fb5ab4f806 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -73,6 +73,8 @@ They may be used to access the members of `x` using Carbon's `x.(C.M)` or `x.(C.f)`. What is their type? Can they be passed to a function separately from the instance of `C` to bind with it? +The expression `x.M` on the other hand doesn't have a trivial correspondence in C++ despite being a useful to bind a specific instance and produce a stand-alone callable object. We would like a model that allows `x.M` to be meaningful in a way that is consistent with the existing meaning of `x.f` and generalizes well across different kinds of methods and callables. + The other issue is how we clearly delineate the `self` associated with a method signature as separate from the `self` of function values. @@ -111,21 +113,21 @@ like other operators. There are three different interfaces used, depending on whether `x` is a value expression, a reference expression, or a facet: ```carbon -// For value `x` with type `T` and `y` of type `U`, +// For a value expression `x` with type `T` and an expression `y` of type `U`, // `x.(y)` is `y.((U as ValueBind(T)).Op)(x)` interface ValueBind(T:! type) { let Result:! type; fn Op[self: Self](x: T) -> Result; } -// For reference expression `var x: T` and `y` of type `U`, +// For a reference expression `x` using a binding `var x: T` and an expression `y` of type `U`, // `x.(y)` is `*y.((U as RefBind(T)).Op)(&x)` interface RefBind(T:! type) { let Result:! type; fn Op[self: Self](p: T*) -> Result*; } -// For a facet value `T`, `y` of type `U`, +// For a facet value, which includes all type values, `T` and an expression `y` of type `U`, // `T.(y)` is `y.((U as TypeBind(T)).Op)()`. interface TypeBind(T:! type) { let Result:! type; @@ -143,7 +145,7 @@ by how they rewrite into the `x.(y)` form using these two rules: described next. - Simple member access of a facet `T`, as in `T.y`, is not rewritten into the `T.(`\_\_\_`)` form. -- `x->y` is interpreted as `(*x).y`. +- `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): @@ -330,10 +332,10 @@ v.F() == v.(C.F)() == (v as __ValueBind_C_F)() == (v as __ValueBind_C_F).(Call(()).Op)() -// `v` is a reference expression and so uses `RefBind` +// `r` is a reference expression and so uses `RefBind` r.F() == r.(C.F)() == r.(__C_F)() - == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&v)))() + == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&r)))() == (*(&v as __RefBind_C_F*))() == (*(&v as __RefBind_C_F*)).(Call(()).Op)() ``` @@ -642,7 +644,7 @@ pointers-to-members, that include the offset information in the runtime object state. So we can define binding implementations for them so that they may be used with Carbon's `.(`**`)` and `->(`**`)` operators. -For example, this is how I would expect C++ code to call the above Carbon +For example, this is how we expect C++ code to call the above Carbon functions: ```cpp @@ -1035,7 +1037,7 @@ the definition of `operator*` corresponding to an `operator->`, a workaround would be just to define `operator*`. Other cases of divergence between those operators should be rare, since that is -both surprising to users and for the common case of iterators, violate the C++ +both surprising to users and for the common case of iterators, violates the C++ requirements. If necessary, we can in the future introduce a specific construct just for C++ interop that invokes the C++ arrow operator, such as `CppArrowOperator(x)`, that returns a pointer. @@ -1093,7 +1095,7 @@ This proposal is about: step of using the members of a type. - Being consistent with our overall strategy for defining operators in terms of interface implementations. -- Allows binding-related functionality to be define through +- Allows binding-related functionality to be defined through [library APIs](/docs/project/principles/library_apis_only.md). - Increases uniformity by making member names into ordinary values with types. - Adds expressiveness, enabling member forwarding, passing a member as an From 58e6d25e2f0fa3d81fd706e7e3cd5c3014b64bea Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:14:53 -0700 Subject: [PATCH 40/77] Apply suggestions from code review Co-authored-by: Carbon Infra Bot --- proposals/p3720.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 392fb5ab4f806..12980b305d152 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -73,7 +73,11 @@ They may be used to access the members of `x` using Carbon's `x.(C.M)` or `x.(C.f)`. What is their type? Can they be passed to a function separately from the instance of `C` to bind with it? -The expression `x.M` on the other hand doesn't have a trivial correspondence in C++ despite being a useful to bind a specific instance and produce a stand-alone callable object. We would like a model that allows `x.M` to be meaningful in a way that is consistent with the existing meaning of `x.f` and generalizes well across different kinds of methods and callables. +The expression `x.M` on the other hand doesn't have a trivial correspondence in +C++ despite being a useful to bind a specific instance and produce a stand-alone +callable object. We would like a model that allows `x.M` to be meaningful in a +way that is consistent with the existing meaning of `x.f` and generalizes well +across different kinds of methods and callables. The other issue is how we clearly delineate the `self` associated with a method signature as separate from the `self` of function values. @@ -644,8 +648,7 @@ pointers-to-members, that include the offset information in the runtime object state. So we can define binding implementations for them so that they may be used with Carbon's `.(`**`)` and `->(`**`)` operators. -For example, this is how we expect C++ code to call the above Carbon -functions: +For example, this is how we expect C++ code to call the above Carbon functions: ```cpp struct C { From 04f896dd4337fc122156dbed15e0d4b330e4e294 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 18 Mar 2024 23:43:49 +0000 Subject: [PATCH 41/77] Fix using `r` instead of `v` --- proposals/p3720.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 12980b305d152..f6648ec0d6365 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -340,8 +340,8 @@ v.F() == v.(C.F)() r.F() == r.(C.F)() == r.(__C_F)() == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&r)))() - == (*(&v as __RefBind_C_F*))() - == (*(&v as __RefBind_C_F*)).(Call(()).Op)() + == (*(&r as __RefBind_C_F*))() + == (*(&r as __RefBind_C_F*)).(Call(()).Op)() ``` > **Note:** This rewrite results in a method call, but the rewrite is only From caef6d42df50d520ad619afe87f520002df955f0 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 19 Mar 2024 01:52:20 +0000 Subject: [PATCH 42/77] Changes based on feedback --- proposals/p3720.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index f6648ec0d6365..aeb54e01f9680 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -365,8 +365,8 @@ impl __RefBind_C_Static as Call(()) with .Result = i32; impl __TypeOf_C_Static as Call(()) where .Result = i32; ``` -**Note**: We could reduce the number of implementations required by making -`__RefBind_*` an +**Future work**: We could reduce the number of implementations required by +making `__RefBind_*` an [extending adapter](/docs/design/generics/details.md#extending-adapter) of the corresponding `__ValueBind_*` class. @@ -722,6 +722,8 @@ impl C as I { Results in this implementation: ```carbon +fn C_I_F(__self: C + impl __ValueBind_I_F(C) as Call(()) where .Result = () { fn Op[self: Self]() { let __self: C = self as C; @@ -737,9 +739,11 @@ c.(I.F)() == c.(__I_F)() == __I_F.((__TypeOf_I_F as ValueBind(C)).Op)(c)() == (c as __ValueBind_I_F(C))() == ((c as __ValueBind_I_F(C)) as Call(())).Op() - == Fanfare(c) ``` +Which results in invoking the above implementation that will ultimately call +`Fanfare(c)`. + ### Non-instance interface members Non-instance members use the `TypeBind` interface instead. For example, if `G` @@ -799,9 +803,10 @@ C.(J.G)() == C.(__J_G)() == __J_G.((__TypeOf_J_G as TypeBind(C)).Op)()() == ({} as __TypeBind_J_G(C))() == (({} as __TypeBind_J_G(C)) as Call(())).Op() - == Fireworks() ``` +Which calls the above implementation that calls `Fireworks()`. + > **Note:** Binding for non-instance members doesn't work with `ValueBind`, we > need `TypeBind`. Otherwise there is no way to get the value `C` into the > result type. Furthermore, we want `TypeBind` implementation no matter which From c5168c922106bba2d533f283059f12050063b104 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 20 Mar 2024 22:09:48 +0000 Subject: [PATCH 43/77] Remove best-effort rule, make it a lint instead --- proposals/p3720.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index aeb54e01f9680..5eb4664872ecc 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -38,6 +38,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [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) @@ -262,10 +263,12 @@ How does this arise? > [member_access.md](/docs/design/expressions/member_access.md) says that > `v.(C.Static)` and `r.(C.Static)` are both invalid, because they don't perform > member name lookup, instance binding, nor impl lookup -- the `v.` and `r.` -> portions are redundant. That rule is now a best-effort diagnostic and should -> not trigger as the result of rewrites performed by the compiler or when the -> problem is not contained in a single expression. For example, it would be -> reasonable for the compiler to allow: +> portions are redundant. That rule is removed by this proposal. +> +> Instead, tools such as linters can option such code as suspicious on a +> best-effort basis, particularly when the issue is contained in a single +> expression. Such tools may still allow code that performs the same operation +> across multiple statements, as in: > > ```carbon > let M:! auto = C.Static @@ -273,11 +276,11 @@ How does this arise? > r.(M)(); > ``` > -> Note that with overloaded names, the `M` might be an instance member in some +> Note that if `M` is an overloaded name, it could be an instance member in some > cases and a non-instance member in others, depending on the arguments passed. -> Since the call happens after binding, this gives another reason to consider -> this a best-effort error on a whole expression, rather than a strict rule just -> about binding. +> This is another reason to delegate this to linters on a best-effort error +> basis analyzing a whole expression, rather than a strict rule just about +> binding. The binding operators are defined using three dedicated interfaces: @@ -1266,3 +1269,8 @@ 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 9a922ad2e7b1fdbc69a943485f522a3a50a58b86 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 20 Mar 2024 23:12:38 +0000 Subject: [PATCH 44/77] Address feedback, delegating `expand api` work to a future proposal --- proposals/p3720.md | 84 +++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 5eb4664872ecc..ba306156db742 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -30,8 +30,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [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) - [Future: properties](#future-properties) + - [Future: building block for language features such as API extension](#future-building-block-for-language-features-such-as-api-extension) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) - [Swap the bind interface parameters](#swap-the-bind-interface-parameters) @@ -140,6 +142,9 @@ interface TypeBind(T:! type) { } ``` +> **Note:** `TypeBind` is its own binding interface since the members of a type +> are defined by their values, not by their type. + The other member access operators -- `x.y`, `x->y`, and `x->(y)` -- are defined by how they rewrite into the `x.(y)` form using these two rules: @@ -278,36 +283,14 @@ How does this arise? > > Note that if `M` is an overloaded name, it could be an instance member in some > cases and a non-instance member in others, depending on the arguments passed. -> This is another reason to delegate this to linters on a best-effort error -> basis analyzing a whole expression, rather than a strict rule just about +> This is another reason to delegate this to linters analyzing a whole +> expression on a best-effort basis, rather than a strict rule just about > binding. -The binding operators are defined using three dedicated interfaces: - -```carbon -// For value `x` with type `T` and `y` of type `U`, -// `x.(y)` is `y.((U as ValueBind(T)).Op)(x)` -interface ValueBind(T:! type) { - let Result:! type; - fn Op[self: Self](x: T) -> Result; -} - -// For reference expression `var x: T` and `y` of type `U`, -// `x.(y)` is `*y.((U as RefBind(T)).Op)(&x)` -interface RefBind(T:! type) { - let Result:! type; - fn Op[self: Self](p: T*) -> Result*; -} - -// For a facet value `T`, `y` of type `U`, -// `T.(y)` is `y.((U as TypeBind(T)).Op)()`. -interface TypeBind(T:! type) { - let Result:! type; - fn Op[self: Self]() -> Result; -} -``` - -These binding operations are implemented for the types of the class members: +The binding operators are defined using three dedicated interfaces -- +`ValueBind`, `RefBind`, and `TypeBind` -- +[as defined in the "proposal" section](#proposal). These binding operations are +implemented for the types of the class members: ```carbon impl __TypeOf_C_F as ValueBind(C) @@ -875,12 +858,18 @@ 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 general rule 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. The extended type -must be complete. 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. +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 @@ -892,7 +881,12 @@ 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. +> 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 @@ -941,6 +935,18 @@ With these two ingredients, `b.Search(32)` is equivalent to ### 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). @@ -1058,6 +1064,8 @@ just for C++ interop that invokes the C++ arrow operator, such as and in [a comment on this proposal](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1507917882). +## Future work + ### Future: tuple indexing We can reframe the use of the compound member access syntax for tuple fields as @@ -1098,6 +1106,12 @@ interfaces. The compiler would try them in a specified order and use the one it found first. This has the downside of the possibility of different behavior in a checked generic context where only some of the implementations are visible. +### Future: building block for language features such as API extension + +We should be able to express other language features, such as API extension, in +terms of customized binding, plus possibly some new language primitives. This +should be explored in a future proposal. + ## Rationale This proposal is about: From 8df97205293ce0a41c6181921fea695aa1b2eb5a Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 20 Mar 2024 23:23:08 +0000 Subject: [PATCH 45/77] Delete content moved to #3802 --- proposals/p3720.md | 321 +-------------------------------------------- 1 file changed, 6 insertions(+), 315 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index ba306156db742..d5a6d98aaf984 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -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) @@ -39,8 +35,6 @@ 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) @@ -48,8 +42,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 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 @@ -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 @@ -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 @@ -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) From fd5815d52b1199072e98fc4b54e5f6f28a0e76d4 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 22 Mar 2024 20:40:47 +0000 Subject: [PATCH 46/77] Alternative --- proposals/p3720.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index d5a6d98aaf984..305d088184dee 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -35,6 +35,7 @@ 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) + - [Non-instance members are idempotent under binding](#non-instance-members-are-idempotent-under-binding) @@ -979,3 +980,64 @@ 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)). + +### Non-instance members are idempotent under binding + +In the current proposal, binding of non-instance members results in an adapter +type, the same as an instance member. For example, + +```carbon +class C { + fn Static() -> i32; +} +``` + +is translated into something like: + +> **Current proposal:** +> +> ```carbon +> class __TypeOf_C_Static {} +> let __C_Static:! __TypeOf_C_Static = {}; +> +> class __ValueBind_C_Static { +> adapt C; +> } +> impl __TypeOf_C_Static as ValueBind(C) +> where .Result = __ValueBind_C_Static; +> +> class __RefBind_C_Static { +> adapt C; +> } +> impl __TypeOf_C_Static as RefBind(C) +> where .Result = __RefBind_C_Static; +> ``` + +An alternative is that binding of a non-instance member is idempotent, so there +is no `__ValueBind_C_Static` type and `ValueBind(C)` results in a value of type +`__TypeOf_C_Static` instead: + +> **Alternative:** +> +> ```carbon +> class __TypeOf_C_Static {} +> // Might need to be a `var` instead? +> let __C_Static:! __TypeOf_C_Static = {}; +> +> impl __TypeOf_C_Static as ValueBind(C) +> where .Result = __TypeOf_C_Static; +> impl __TypeOf_C_Static as RefBind(C) +> where .Result = __TypeOf_C_Static; +> ``` + +There are a few concerns with this alternative: + +- This is less consistent with the instance member case. +- There would be a discontinuity when adding an instance overload to a name + that was previously only a non-instance member. +- Reference binding is trickier, since it would have to return the address of + an object of type `__TypeOf_C_Static`. Perhaps a global variable? +- The current proposal rejects `v.(v.(C.Static))`, which is desirable. + +This was discussed in +[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513681915). From 35de7d2821c3a9de8ce022879abd83a745b651cd Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Wed, 27 Mar 2024 20:22:37 -0700 Subject: [PATCH 47/77] Apply suggestions from code review Co-authored-by: Chandler Carruth --- proposals/p3720.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 305d088184dee..c6b278298fc2c 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -109,7 +109,7 @@ difference between functions and methods, however, is in scope. ## Proposal We propose that Carbon defines the compound member access operator, specifically -`x.(y)`, to be defined in terms of rewrites to invoking an interface method, +`x.(y)`, in terms of rewrites to invoking an interface method, like other operators. There are three different interfaces used, depending on whether `x` is a value expression, a reference expression, or a facet: @@ -145,8 +145,7 @@ by how they rewrite into the `x.(y)` form using these two rules: - `x.y` is interpreted using the existing [member resolution rules](/docs/design/expressions/member_access.md#member-resolution). For example, `x.y` is treated as `x.(T.y)` for non-type values `x` with type - `T`. These rules are expanded to support `extend api T` declarations - described next. + `T`. - Simple member access of a facet `T`, as in `T.y`, is not rewritten into the `T.(`\_\_\_`)` form. - `x->y` and `x->(y)` are interpreted as `(*x).y` and `(*x).(y)` respectively. @@ -227,7 +226,7 @@ How does this arise? > member name lookup, instance binding, nor impl lookup -- the `v.` and `r.` > portions are redundant. That rule is removed by this proposal. > -> Instead, tools such as linters can option such code as suspicious on a +> Instead, tools such as linters can flag such code as suspicious on a > best-effort basis, particularly when the issue is contained in a single > expression. Such tools may still allow code that performs the same operation > across multiple statements, as in: @@ -669,8 +668,7 @@ fn C_I_F(__self: C impl __ValueBind_I_F(C) as Call(()) where .Result = () { fn Op[self: Self]() { - let __self: C = self as C; - Fanfare(__self); + call_compiler_intrinsic((C as I).F, self as C, ()); } } ``` From 27857685dc3c57e0f66664ff3cfc2fb898a8ab69 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 28 Mar 2024 03:26:21 +0000 Subject: [PATCH 48/77] Fix formatting --- proposals/p3720.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index c6b278298fc2c..8477634a5a643 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -109,9 +109,9 @@ difference between functions and methods, however, is in scope. ## Proposal We propose that Carbon defines the compound member access operator, specifically -`x.(y)`, in terms of rewrites to invoking an interface method, -like other operators. There are three different interfaces used, depending on -whether `x` is a value expression, a reference expression, or a facet: +`x.(y)`, in terms of rewrites to invoking an interface method, like other +operators. There are three different interfaces used, depending on whether `x` +is a value expression, a reference expression, or a facet: ```carbon // For a value expression `x` with type `T` and an expression `y` of type `U`, @@ -226,7 +226,7 @@ How does this arise? > member name lookup, instance binding, nor impl lookup -- the `v.` and `r.` > portions are redundant. That rule is removed by this proposal. > -> Instead, tools such as linters can flag such code as suspicious on a +> Instead, tools such as linters can option such code as suspicious on a > best-effort basis, particularly when the issue is contained in a single > expression. Such tools may still allow code that performs the same operation > across multiple statements, as in: From f6203032c209bea002c4de1264a90a867585339a Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 28 Mar 2024 03:32:33 +0000 Subject: [PATCH 49/77] Add reference to "delegates" in other languages --- proposals/p3720.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index 8477634a5a643..f9c450440f102 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -106,6 +106,10 @@ parameters. That is out of scope of this proposal, and will be addressed separately, and means that `addr self` methods won't be considered here. The difference between functions and methods, however, is in scope. +Other languages, such as C#, D, and Virgil, have constructs that represent bound +and unbound methods, such as +["delegates"](https://news.ycombinator.com/item?id=39812565). + ## Proposal We propose that Carbon defines the compound member access operator, specifically From 886c773bd1aaaf75c8659f9a8683047d1d7c447a Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 28 Mar 2024 03:51:00 +0000 Subject: [PATCH 50/77] Use compiler intrinsics for calls --- proposals/p3720.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index f9c450440f102..2f721b043f27b 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -281,6 +281,7 @@ v.F() == v.(C.F)() == __C_F.((__TypeOf_C_F as ValueBind(C)).Op(v))() == (v as __ValueBind_C_F)() == (v as __ValueBind_C_F).(Call(()).Op)() + == method_call_compiler_intrinsic(C.F, v, ()) // `r` is a reference expression and so uses `RefBind` r.F() == r.(C.F)() @@ -288,6 +289,7 @@ r.F() == r.(C.F)() == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&r)))() == (*(&r as __RefBind_C_F*))() == (*(&r as __RefBind_C_F*)).(Call(()).Op)() + == method_call_compiler_intrinsic(__C_F, r, ()) ``` > **Note:** This rewrite results in a method call, but the rewrite is only @@ -299,14 +301,28 @@ types. ```carbon // Since `C.F` takes `self: Self` it can be used with both // value and reference expressions: -impl __ValueBind_C_F as Call(()) with .Result = i32; +impl __ValueBind_C_F as Call(()) with .Result = i32 { + // Implementation of `Call(())` for `__RefBind_C_F` is the same. + fn Op[self: Self]() -> i32 { + // Calls `(self as C).(C.F)()`, but without triggering + // binding again. + return method_call_compiler_intrinsic(__C_F, self as C, ()); + } +} + impl __RefBind_C_F as Call(()) with .Result = i32; // `C.Static` works the same as `C.F`, except it also // implements the call interfaces on `__TypeOf_C_Static`. // This allows `C.Static()` to work, in addition to // `v.Static()` and `r.Static()`. -impl __ValueBind_C_Static as Call(()) with .Result = i32; +impl __ValueBind_C_Static as Call(()) with .Result = i32 { + // Other implementations of `Call(())` are the same. + fn Op[unused self: Self]() -> i32 { + // Calls `C.Static()`, without triggering binding again. + return call_compiler_intrinsic(__C_Static, ()); + } +} impl __RefBind_C_Static as Call(()) with .Result = i32; impl __TypeOf_C_Static as Call(()) where .Result = i32; ``` From 055bbaaa341e9aa5d0a4c5f73097edc13ca31bb7 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 28 Mar 2024 23:13:14 +0000 Subject: [PATCH 51/77] Better delegate links --- proposals/p3720.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 2f721b043f27b..e1e608881a7a4 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -106,9 +106,10 @@ parameters. That is out of scope of this proposal, and will be addressed separately, and means that `addr self` methods won't be considered here. The difference between functions and methods, however, is in scope. -Other languages, such as C#, D, and Virgil, have constructs that represent bound -and unbound methods, such as -["delegates"](https://news.ycombinator.com/item?id=39812565). +Other languages, such as +[C#](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/using-delegates) +and [D](https://tour.dlang.org/tour/en/basics/delegates), have constructs that +represent bound and unbound methods, such as "delegates". ## Proposal From 91dee3a66ddccd4e2def1d87ca978288e5a7df79 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 28 Mar 2024 23:23:40 +0000 Subject: [PATCH 52/77] Argument to call intrinsics should be a specific function body --- proposals/p3720.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index e1e608881a7a4..dee8e1a259dcd 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -282,7 +282,8 @@ v.F() == v.(C.F)() == __C_F.((__TypeOf_C_F as ValueBind(C)).Op(v))() == (v as __ValueBind_C_F)() == (v as __ValueBind_C_F).(Call(()).Op)() - == method_call_compiler_intrinsic(C.F, v, ()) + == method_call_compiler_intrinsic( + , v, ()) // `r` is a reference expression and so uses `RefBind` r.F() == r.(C.F)() @@ -290,7 +291,8 @@ r.F() == r.(C.F)() == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&r)))() == (*(&r as __RefBind_C_F*))() == (*(&r as __RefBind_C_F*)).(Call(()).Op)() - == method_call_compiler_intrinsic(__C_F, r, ()) + == method_call_compiler_intrinsic( + , r, ()) ``` > **Note:** This rewrite results in a method call, but the rewrite is only @@ -307,7 +309,8 @@ impl __ValueBind_C_F as Call(()) with .Result = i32 { fn Op[self: Self]() -> i32 { // Calls `(self as C).(C.F)()`, but without triggering // binding again. - return method_call_compiler_intrinsic(__C_F, self as C, ()); + return method_call_compiler_intrinsic( + , self as C, ()); } } @@ -321,7 +324,7 @@ impl __ValueBind_C_Static as Call(()) with .Result = i32 { // Other implementations of `Call(())` are the same. fn Op[unused self: Self]() -> i32 { // Calls `C.Static()`, without triggering binding again. - return call_compiler_intrinsic(__C_Static, ()); + return call_compiler_intrinsic(, ()); } } impl __RefBind_C_Static as Call(()) with .Result = i32; From 152f77e23927029e491b470f1701233160087d4f Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 28 Mar 2024 23:27:09 +0000 Subject: [PATCH 53/77] Checkpoint progress. --- proposals/p3720.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index dee8e1a259dcd..50694d675400a 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -283,7 +283,7 @@ v.F() == v.(C.F)() == (v as __ValueBind_C_F)() == (v as __ValueBind_C_F).(Call(()).Op)() == method_call_compiler_intrinsic( - , v, ()) + , v, ()) // `r` is a reference expression and so uses `RefBind` r.F() == r.(C.F)() @@ -292,7 +292,7 @@ r.F() == r.(C.F)() == (*(&r as __RefBind_C_F*))() == (*(&r as __RefBind_C_F*)).(Call(()).Op)() == method_call_compiler_intrinsic( - , r, ()) + , r, ()) ``` > **Note:** This rewrite results in a method call, but the rewrite is only @@ -310,7 +310,7 @@ impl __ValueBind_C_F as Call(()) with .Result = i32 { // Calls `(self as C).(C.F)()`, but without triggering // binding again. return method_call_compiler_intrinsic( - , self as C, ()); + , self as C, ()); } } @@ -324,7 +324,8 @@ impl __ValueBind_C_Static as Call(()) with .Result = i32 { // Other implementations of `Call(())` are the same. fn Op[unused self: Self]() -> i32 { // Calls `C.Static()`, without triggering binding again. - return call_compiler_intrinsic(, ()); + return call_compiler_intrinsic( + , ()); } } impl __RefBind_C_Static as Call(()) with .Result = i32; @@ -692,7 +693,8 @@ fn C_I_F(__self: C impl __ValueBind_I_F(C) as Call(()) where .Result = () { fn Op[self: Self]() { - call_compiler_intrinsic((C as I).F, self as C, ()); + call_compiler_intrinsic( + , self as C, ()); } } ``` From 0a477869dbc638d949f0b0e25adfe5f31105b395 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 28 Mar 2024 23:37:44 +0000 Subject: [PATCH 54/77] Checkpoint progress. --- proposals/p3720.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index 50694d675400a..3c7c170b1680f 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -282,6 +282,9 @@ v.F() == v.(C.F)() == __C_F.((__TypeOf_C_F as ValueBind(C)).Op(v))() == (v as __ValueBind_C_F)() == (v as __ValueBind_C_F).(Call(()).Op)() + == method_call_compiler_intrinsic( + , + (v as __ValueBind_C_F) as C, ()) == method_call_compiler_intrinsic( , v, ()) @@ -291,6 +294,9 @@ r.F() == r.(C.F)() == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&r)))() == (*(&r as __RefBind_C_F*))() == (*(&r as __RefBind_C_F*)).(Call(()).Op)() + == method_call_compiler_intrinsic( + , + *(&r as __RefBind_C_F*) as C, ()) == method_call_compiler_intrinsic( , r, ()) ``` From ba070e7237d5ff8163458031b02535497b83c203 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 28 Mar 2024 23:46:23 +0000 Subject: [PATCH 55/77] Checkpoint progress. --- proposals/p3720.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 3c7c170b1680f..7894c37563adf 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -282,6 +282,9 @@ v.F() == v.(C.F)() == __C_F.((__TypeOf_C_F as ValueBind(C)).Op(v))() == (v as __ValueBind_C_F)() == (v as __ValueBind_C_F).(Call(()).Op)() + == method_call_compiler_intrinsic( + , + v as __ValueBind_C_F, ()); == method_call_compiler_intrinsic( , (v as __ValueBind_C_F) as C, ()) @@ -294,11 +297,15 @@ r.F() == r.(C.F)() == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&r)))() == (*(&r as __RefBind_C_F*))() == (*(&r as __RefBind_C_F*)).(Call(()).Op)() + == method_call_compiler_intrinsic( + , + *(&r as __RefBind_C_F*) , ()); == method_call_compiler_intrinsic( , *(&r as __RefBind_C_F*) as C, ()) == method_call_compiler_intrinsic( - , r, ()) + , + r , ()) ``` > **Note:** This rewrite results in a method call, but the rewrite is only From dd1d6112fd1e1a52d342164e6a81143b6b4a83ed Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 28 Mar 2024 23:56:30 +0000 Subject: [PATCH 56/77] Checkpoint progress. --- proposals/p3720.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index 7894c37563adf..7e3b6e8bb70a7 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -282,6 +282,7 @@ v.F() == v.(C.F)() == __C_F.((__TypeOf_C_F as ValueBind(C)).Op(v))() == (v as __ValueBind_C_F)() == (v as __ValueBind_C_F).(Call(()).Op)() + == == method_call_compiler_intrinsic( , v as __ValueBind_C_F, ()); @@ -297,6 +298,7 @@ r.F() == r.(C.F)() == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&r)))() == (*(&r as __RefBind_C_F*))() == (*(&r as __RefBind_C_F*)).(Call(()).Op)() + == == method_call_compiler_intrinsic( , *(&r as __RefBind_C_F*) , ()); @@ -308,6 +310,9 @@ r.F() == r.(C.F)() r , ()) ``` +> **Note:** the interface member lookup step is described in the +> ["instance interface members" section](#instance-interface-members). + > **Note:** This rewrite results in a method call, but the rewrite is only > applied once, not applied again to the resulting method call. From 99fb69aaaa24bd502d71cd4259f37cfa8346b244 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 29 Mar 2024 00:57:35 +0000 Subject: [PATCH 57/77] Checkpoint progress. --- proposals/p3720.md | 79 ++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 7e3b6e8bb70a7..3d0a32598a6a2 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -273,7 +273,7 @@ impl __TypeOf_C_F as RefBind(C) > [non-instance interface members](#non-instance-interface-members). Those implementations are how we get from `__C_F` with type `__TypeOf_C_F` to -`v as __ValueBind_C_F` or `r as __RefBind_C_F`: +`v as __ValueBind_C_F` or `&r as __RefBind_C_F*`: ```carbon // `v` is a value and so uses `ValueBind` @@ -281,40 +281,18 @@ v.F() == v.(C.F)() == v.(__C_F)() == __C_F.((__TypeOf_C_F as ValueBind(C)).Op(v))() == (v as __ValueBind_C_F)() - == (v as __ValueBind_C_F).(Call(()).Op)() - == - == method_call_compiler_intrinsic( - , - v as __ValueBind_C_F, ()); - == method_call_compiler_intrinsic( - , - (v as __ValueBind_C_F) as C, ()) - == method_call_compiler_intrinsic( - , v, ()) // `r` is a reference expression and so uses `RefBind` r.F() == r.(C.F)() == r.(__C_F)() == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&r)))() == (*(&r as __RefBind_C_F*))() - == (*(&r as __RefBind_C_F*)).(Call(()).Op)() - == - == method_call_compiler_intrinsic( - , - *(&r as __RefBind_C_F*) , ()); - == method_call_compiler_intrinsic( - , - *(&r as __RefBind_C_F*) as C, ()) - == method_call_compiler_intrinsic( - , - r , ()) ``` -> **Note:** the interface member lookup step is described in the -> ["instance interface members" section](#instance-interface-members). - -> **Note:** This rewrite results in a method call, but the rewrite is only -> applied once, not applied again to the resulting method call. +At this point we have resolved the binding, and are left with an expression +followed by `()`. In the first case, that expression is a value expression of +type `__Value_Bind_C_F`. In the second case, it is a reference expression of +type `__RefBind_C_F`. The last ingredient is the implementation of the call interfaces for these bound types. @@ -355,6 +333,46 @@ making `__RefBind_*` an [extending adapter](/docs/design/generics/details.md#extending-adapter) of the corresponding `__ValueBind_*` class. +Going back to `v.F()` and `r.F()`, after binding the next step is to resolve the +call. As described in +[proposal #2875](https://github.com/carbon-language/carbon-lang/pull/2875), this +call is rewritten to an invocation of the `Op` method of the `Call(())` +interface, using the implementations just defined. Note: + +- Passing `*(&r as __RefBind_C_F*)` to the `self` parameter of `Call(()).Op` + converts the reference expression to a value. +- The `Call` interface is special. We don't + [rewrite](#instance-interface-members) calls to `Call(__).Op` to avoid + infinite recursion. + +```carbon +v.F() == (v as __ValueBind_C_F)() + == (v as __ValueBind_C_F).((__ValueBind_C_F as Call(())).Op)() + == method_call_compiler_intrinsic( + , + v as __ValueBind_C_F, ()); + == method_call_compiler_intrinsic( + , + (v as __ValueBind_C_F) as C, ()) + == method_call_compiler_intrinsic( + , v, ()) + +r.F() == (*(&r as __RefBind_C_F*))() + == (*(&r as __RefBind_C_F*)).((__RefBind_C_F as Call(())).Op)() + == method_call_compiler_intrinsic( + , + *(&r as __RefBind_C_F*) , ()); + == method_call_compiler_intrinsic( + , + *(&r as __RefBind_C_F*) as C, ()) + == method_call_compiler_intrinsic( + , + r , ()) +``` + +> **Note:** This rewrite results in compiler intrinsics for calling. This is to +> show that no more rewrites are applied. + ### Inheritance and other implicit conversions Now consider methods of a base class: @@ -707,8 +725,6 @@ impl C as I { Results in this implementation: ```carbon -fn C_I_F(__self: C - impl __ValueBind_I_F(C) as Call(()) where .Result = () { fn Op[self: Self]() { call_compiler_intrinsic( @@ -723,12 +739,15 @@ A call such as `c.(I.F)()` goes through these rewrites: c.(I.F)() == c.(__I_F)() == __I_F.((__TypeOf_I_F as ValueBind(C)).Op)(c)() == (c as __ValueBind_I_F(C))() - == ((c as __ValueBind_I_F(C)) as Call(())).Op() + == (c as __ValueBind_I_F(C)).((__ValueBind_I_F(C) as Call(())).Op)() ``` Which results in invoking the above implementation that will ultimately call `Fanfare(c)`. +> **Note:** The `Call` interface gets special treatment and does not get these +> rewrites to avoid recursing forever. + ### Non-instance interface members Non-instance members use the `TypeBind` interface instead. For example, if `G` From 6f5b5a10442954291d0bf27d43a8256c470713b9 Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Tue, 14 May 2024 11:46:58 -0700 Subject: [PATCH 58/77] Update proposals/p3720.md Co-authored-by: Chandler Carruth --- proposals/p3720.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 3d0a32598a6a2..237861bb6b561 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -231,7 +231,7 @@ How does this arise? > member name lookup, instance binding, nor impl lookup -- the `v.` and `r.` > portions are redundant. That rule is removed by this proposal. > -> Instead, tools such as linters can option such code as suspicious on a +> Instead, tools such as linters can flag such code as suspicious on a > best-effort basis, particularly when the issue is contained in a single > expression. Such tools may still allow code that performs the same operation > across multiple statements, as in: From 1d2d25a4f1437b8626117aab509187819936463c Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Tue, 14 May 2024 11:48:28 -0700 Subject: [PATCH 59/77] Update proposals/p3720.md Co-authored-by: Richard Smith --- proposals/p3720.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 237861bb6b561..e60cdd37e9f46 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -256,7 +256,7 @@ implemented for the types of the class members: ```carbon impl __TypeOf_C_F as ValueBind(C) where .Result = __ValueBind_C_F { - fn Op[self: Self](x: C) -> __ValueBind_C_F { + fn Op[unused self: Self](x: C) -> __ValueBind_C_F { return x as __ValueBind_C_F; } } From 0e53d1bd6e1755edb3c3fe0ea97b82daf1dc3609 Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Tue, 14 May 2024 11:48:39 -0700 Subject: [PATCH 60/77] Update proposals/p3720.md Co-authored-by: Richard Smith --- proposals/p3720.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index e60cdd37e9f46..a3232cffaff35 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -263,7 +263,7 @@ impl __TypeOf_C_F as ValueBind(C) impl __TypeOf_C_F as RefBind(C) where .Result = __RefBind_C_F { - fn Op[self: Self](p: C*) -> __RefBind_C_F* { + fn Op[unused self: Self](p: C*) -> __RefBind_C_F* { return p as __RefBind_C_F*; } } From 9dc634086b113d7c61a8fe604d7dbe08e84c2a62 Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Tue, 14 May 2024 11:50:50 -0700 Subject: [PATCH 61/77] Apply suggestions from code review Co-authored-by: Richard Smith --- proposals/p3720.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index a3232cffaff35..40eef432a37b6 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -279,13 +279,13 @@ Those implementations are how we get from `__C_F` with type `__TypeOf_C_F` to // `v` is a value and so uses `ValueBind` v.F() == v.(C.F)() == v.(__C_F)() - == __C_F.((__TypeOf_C_F as ValueBind(C)).Op(v))() + == __C_F.((__TypeOf_C_F as ValueBind(C)).Op)(v)() == (v as __ValueBind_C_F)() // `r` is a reference expression and so uses `RefBind` r.F() == r.(C.F)() == r.(__C_F)() - == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op(&r)))() + == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op)(&r))() == (*(&r as __RefBind_C_F*))() ``` @@ -305,7 +305,7 @@ impl __ValueBind_C_F as Call(()) with .Result = i32 { fn Op[self: Self]() -> i32 { // Calls `(self as C).(C.F)()`, but without triggering // binding again. - return method_call_compiler_intrinsic( + return inlined_method_call_compiler_intrinsic( , self as C, ()); } } @@ -727,7 +727,7 @@ Results in this implementation: ```carbon impl __ValueBind_I_F(C) as Call(()) where .Result = () { fn Op[self: Self]() { - call_compiler_intrinsic( + inline_method_call_compiler_intrinsic( , self as C, ()); } } From 90d1ebc9957fb1ab9b9d33867cc21fd9f5d355a1 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 14 May 2024 18:51:22 +0000 Subject: [PATCH 62/77] Fix formatting --- proposals/p3720.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 40eef432a37b6..ecf438f36f2c2 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -231,7 +231,7 @@ How does this arise? > member name lookup, instance binding, nor impl lookup -- the `v.` and `r.` > portions are redundant. That rule is removed by this proposal. > -> Instead, tools such as linters can flag such code as suspicious on a +> Instead, tools such as linters can option such code as suspicious on a > best-effort basis, particularly when the issue is contained in a single > expression. Such tools may still allow code that performs the same operation > across multiple statements, as in: From 0539e3db1bac3fe564f9ad5abbc169c9eefad48c Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 14 May 2024 18:56:58 +0000 Subject: [PATCH 63/77] Use "highlight" since "flag" is rewritten to "option" by check-google-doc-style --- proposals/p3720.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index ecf438f36f2c2..f510d583ca528 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -231,7 +231,7 @@ How does this arise? > member name lookup, instance binding, nor impl lookup -- the `v.` and `r.` > portions are redundant. That rule is removed by this proposal. > -> Instead, tools such as linters can option such code as suspicious on a +> Instead, tools such as linters can highlight such code as suspicious on a > best-effort basis, particularly when the issue is contained in a single > expression. Such tools may still allow code that performs the same operation > across multiple statements, as in: From 6e7ed827a866a4a01746217aeb50cdd969dd4cb1 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 14 May 2024 20:06:53 +0000 Subject: [PATCH 64/77] Checkpoint progress. --- proposals/p3720.md | 182 ++++++++++++++++++++++++++------------------- 1 file changed, 107 insertions(+), 75 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index f510d583ca528..b4f16e54403ac 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -36,6 +36,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [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) - [Non-instance members are idempotent under binding](#non-instance-members-are-idempotent-under-binding) + - [Separate `Result` types for `ValueBind` and `RefBind`](#separate-result-types-for-valuebind-and-refbind) @@ -119,22 +120,33 @@ operators. There are three different interfaces used, depending on whether `x` is a value expression, a reference expression, or a facet: ```carbon -// For a value expression `x` with type `T` and an expression `y` of type `U`, -// `x.(y)` is `y.((U as ValueBind(T)).Op)(x)` -interface ValueBind(T:! type) { +// This determines the type of the result of binding. It is a +// separate interface shared by `ValueBind` and `RefBind` to +// ensure they produce the same result type. We don't want the +// type of an expression to depend on the expression category +// of the arguments. +interface Bind(T:! type) { let Result:! type; +} + +// For a value expression `x` with type `T` and an expression +// `y` of type `U`, `x.(y)` is `y.((U as ValueBind(T)).Op)(x)` +interface ValueBind(T:! type) { + extend Bind(T); fn Op[self: Self](x: T) -> Result; } -// For a reference expression `x` using a binding `var x: T` and an expression `y` of type `U`, -// `x.(y)` is `*y.((U as RefBind(T)).Op)(&x)` +// For a reference expression `x` using a binding `var x: T` +// and an expression `y` of type `U`, `x.(y)` is +// `*y.((U as RefBind(T)).Op)(&x)` interface RefBind(T:! type) { - let Result:! type; + extend Bind(T); fn Op[self: Self](p: T*) -> Result*; } -// For a facet value, which includes all type values, `T` and an expression `y` of type `U`, -// `T.(y)` is `y.((U as TypeBind(T)).Op)()`. +// For a facet value, which includes all type values, `T` and +// an expression `y` of type `U`, `T.(y)` is +// `y.((U as TypeBind(T)).Op)()`. interface TypeBind(T:! type) { let Result:! type; fn Op[self: Self]() -> Result; @@ -177,10 +189,7 @@ the type of binding that member with either a `C` value or variable. ```carbon class __TypeOf_C_F {} let __C_F:! __TypeOf_C_F = {}; -class __ValueBind_C_F { - adapt C; -} -class __RefBind_C_F { +class __Bind_C_F { adapt C; } @@ -206,11 +215,11 @@ is interpreted as: ```carbon let v: C = {.x = 3}; -Assert((v as __ValueBind_C_F).(Call(()).Op)() == 8); -Assert((v as __ValueBind_C_Static).(Call(()).Op)() == 2); +Assert((v as __Bind_C_F).(Call(()).Op)() == 8); +Assert((v as __Bind_C_Static).(Call(()).Op)() == 2); var r: C = {.x = 4}; -Assert((r as __RefBind_C_F).(Call(()).Op)() == 9); -Assert((r as __RefBind_C_Static).(Call(()).Op)() == 2); +Assert((r as __Bind_C_F).(Call(()).Op)() == 9); +Assert((r as __Bind_C_Static).(Call(()).Op)() == 2); ``` How does this arise? @@ -255,16 +264,19 @@ implemented for the types of the class members: ```carbon impl __TypeOf_C_F as ValueBind(C) - where .Result = __ValueBind_C_F { - fn Op[unused self: Self](x: C) -> __ValueBind_C_F { - return x as __ValueBind_C_F; + where .Result = __Bind_C_F { + fn Op[unused self: Self](x: C) -> __Bind_C_F { + return x as __Bind_C_F; } } +// Note that the `Result` type has to match, since +// it is an associated type in the `Bind(C)` interface +// that both `ValueBind(C)` and `RefBind(C)` extend. impl __TypeOf_C_F as RefBind(C) - where .Result = __RefBind_C_F { - fn Op[unused self: Self](p: C*) -> __RefBind_C_F* { - return p as __RefBind_C_F*; + where .Result = __Bind_C_F { + fn Op[unused self: Self](p: C*) -> __Bind_C_F* { + return p as __Bind_C_F*; } } ``` @@ -273,35 +285,35 @@ impl __TypeOf_C_F as RefBind(C) > [non-instance interface members](#non-instance-interface-members). Those implementations are how we get from `__C_F` with type `__TypeOf_C_F` to -`v as __ValueBind_C_F` or `&r as __RefBind_C_F*`: +`v as __Bind_C_F` or `&r as __Bind_C_F*`: ```carbon // `v` is a value and so uses `ValueBind` v.F() == v.(C.F)() == v.(__C_F)() == __C_F.((__TypeOf_C_F as ValueBind(C)).Op)(v)() - == (v as __ValueBind_C_F)() + == (v as __Bind_C_F)() // `r` is a reference expression and so uses `RefBind` r.F() == r.(C.F)() == r.(__C_F)() == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op)(&r))() - == (*(&r as __RefBind_C_F*))() + == (*(&r as __Bind_C_F*))() ``` -At this point we have resolved the binding, and are left with an expression -followed by `()`. In the first case, that expression is a value expression of -type `__Value_Bind_C_F`. In the second case, it is a reference expression of -type `__RefBind_C_F`. +At this point we have resolved the binding, and are left with an expression of +type `__Bind_C_F` followed by `()`. In the first case, that expression is a +value expression. In the second case, it is a reference expression. The last ingredient is the implementation of the call interfaces for these bound types. ```carbon -// Since `C.F` takes `self: Self` it can be used with both -// value and reference expressions: -impl __ValueBind_C_F as Call(()) with .Result = i32 { - // Implementation of `Call(())` for `__RefBind_C_F` is the same. +// Binding with `C.F` produces something with type +// `__Bind_C_F` whether it is a value or reference +// expression. Since `C.F` takes `self: Self` it can be +// used in both cases. +impl __Bind_C_F as Call(()) with .Result = i32 { fn Op[self: Self]() -> i32 { // Calls `(self as C).(C.F)()`, but without triggering // binding again. @@ -310,13 +322,11 @@ impl __ValueBind_C_F as Call(()) with .Result = i32 { } } -impl __RefBind_C_F as Call(()) with .Result = i32; - // `C.Static` works the same as `C.F`, except it also // implements the call interfaces on `__TypeOf_C_Static`. // This allows `C.Static()` to work, in addition to // `v.Static()` and `r.Static()`. -impl __ValueBind_C_Static as Call(()) with .Result = i32 { +impl __Bind_C_Static as Call(()) with .Result = i32 { // Other implementations of `Call(())` are the same. fn Op[unused self: Self]() -> i32 { // Calls `C.Static()`, without triggering binding again. @@ -324,47 +334,41 @@ impl __ValueBind_C_Static as Call(()) with .Result = i32 { , ()); } } -impl __RefBind_C_Static as Call(()) with .Result = i32; impl __TypeOf_C_Static as Call(()) where .Result = i32; ``` -**Future work**: We could reduce the number of implementations required by -making `__RefBind_*` an -[extending adapter](/docs/design/generics/details.md#extending-adapter) of the -corresponding `__ValueBind_*` class. - Going back to `v.F()` and `r.F()`, after binding the next step is to resolve the call. As described in [proposal #2875](https://github.com/carbon-language/carbon-lang/pull/2875), this call is rewritten to an invocation of the `Op` method of the `Call(())` interface, using the implementations just defined. Note: -- Passing `*(&r as __RefBind_C_F*)` to the `self` parameter of `Call(()).Op` +- Passing `*(&r as __Bind_C_F*)` to the `self` parameter of `Call(()).Op` converts the reference expression to a value. - The `Call` interface is special. We don't [rewrite](#instance-interface-members) calls to `Call(__).Op` to avoid infinite recursion. ```carbon -v.F() == (v as __ValueBind_C_F)() - == (v as __ValueBind_C_F).((__ValueBind_C_F as Call(())).Op)() +v.F() == (v as __Bind_C_F)() + == (v as __Bind_C_F).((__Bind_C_F as Call(())).Op)() == method_call_compiler_intrinsic( - , - v as __ValueBind_C_F, ()); + , + v as __Bind_C_F, ()); == method_call_compiler_intrinsic( , - (v as __ValueBind_C_F) as C, ()) + (v as __Bind_C_F) as C, ()) == method_call_compiler_intrinsic( , v, ()) -r.F() == (*(&r as __RefBind_C_F*))() - == (*(&r as __RefBind_C_F*)).((__RefBind_C_F as Call(())).Op)() +r.F() == (*(&r as __Bind_C_F*))() + == (*(&r as __Bind_C_F*)).((__Bind_C_F as Call(())).Op)() == method_call_compiler_intrinsic( - , - *(&r as __RefBind_C_F*) , ()); + , + *(&r as __Bind_C_F*) , ()); == method_call_compiler_intrinsic( , - *(&r as __RefBind_C_F*) as C, ()) + *(&r as __Bind_C_F*) as C, ()) == method_call_compiler_intrinsic( , r , ()) @@ -398,17 +402,17 @@ allow implicit conversions: ```carbon impl [T:! ImplicitAs(B)] __TypeOf_B_F as ValueBind(T) - where .Result = __ValueBind_B_F { - fn Op[self: Self](x: T) -> __ValueBind_B_F { - return (x as B) as __ValueBind_B_F; + where .Result = __Bind_B_F { + fn Op[self: Self](x: T) -> __Bind_B_F { + return (x as B) as __Bind_B_F; } } impl [T:! type where .Self* impls ImplicitAs(B*)] __TypeOf_B_F as RefBind(T) - where .Result = __RefBind_B_F { - fn Op[self: Self](p: T*) -> __RefBind_B_F* { - return (p as B*) as __RefBind_B_F*; + where .Result = __Bind_B_F { + fn Op[self: Self](p: T*) -> __Bind_B_F* { + return (p as B*) as __Bind_B_F*; } } ``` @@ -443,7 +447,7 @@ results in an implementation using `Different` instead of `C`: // `C.G` will only bind to values that can implicitly convert // to type `Different`. impl [T:! ImplicitAs(Different)] __TypeOf_C_G as ValueBind(T) - where .Result = __ValueBind_C_G; + where .Result = __Bind_C_G; ``` ### Data fields @@ -698,14 +702,14 @@ let __I_F:! __TypeOf_I_F = {}; That type implements `ValueBind` for any type that implements the interface `I`: ```carbon -class __ValueBind_I_F(T:! I) { +class __Bind_I_F(T:! I) { adapt T; } impl forall [T:! I] __TypeOf_I_F as ValueBind(T) - where .Result = __ValueBind_I_F(T) { - fn Op[self: Self](x: T) -> __ValueBind_I_F(T) { - // Valid since `__ValueBind_I_F(T)` adapts `T`: - return x as __ValueBind_I_F(T); + where .Result = __Bind_I_F(T) { + fn Op[self: Self](x: T) -> __Bind_I_F(T) { + // Valid since `__Bind_I_F(T)` adapts `T`: + return x as __Bind_I_F(T); } } ``` @@ -725,7 +729,7 @@ impl C as I { Results in this implementation: ```carbon -impl __ValueBind_I_F(C) as Call(()) where .Result = () { +impl __Bind_I_F(C) as Call(()) where .Result = () { fn Op[self: Self]() { inline_method_call_compiler_intrinsic( , self as C, ()); @@ -738,8 +742,8 @@ A call such as `c.(I.F)()` goes through these rewrites: ```carbon c.(I.F)() == c.(__I_F)() == __I_F.((__TypeOf_I_F as ValueBind(C)).Op)(c)() - == (c as __ValueBind_I_F(C))() - == (c as __ValueBind_I_F(C)).((__ValueBind_I_F(C) as Call(())).Op)() + == (c as __Bind_I_F(C))() + == (c as __Bind_I_F(C)).((__Bind_I_F(C) as Call(())).Op)() ``` Which results in invoking the above implementation that will ultimately call @@ -1061,21 +1065,19 @@ is translated into something like: > class __TypeOf_C_Static {} > let __C_Static:! __TypeOf_C_Static = {}; > -> class __ValueBind_C_Static { +> class __Bind_C_Static { > adapt C; > } +> > impl __TypeOf_C_Static as ValueBind(C) -> where .Result = __ValueBind_C_Static; +> where .Result = __Bind_C_Static; > -> class __RefBind_C_Static { -> adapt C; -> } > impl __TypeOf_C_Static as RefBind(C) -> where .Result = __RefBind_C_Static; +> where .Result = __Bind_C_Static; > ``` An alternative is that binding of a non-instance member is idempotent, so there -is no `__ValueBind_C_Static` type and `ValueBind(C)` results in a value of type +is no `__Bind_C_Static` type and `ValueBind(C)` results in a value of type `__TypeOf_C_Static` instead: > **Alternative:** @@ -1102,3 +1104,33 @@ There are a few concerns with this alternative: This was discussed in [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513681915). + +### Separate `Result` types for `ValueBind` and `RefBind` + +An earlier iteration of this proposal had separate `Result` associated types for +`ValueBind` and `RefBind`, as in: + +```carbon +interface ValueBind(T:! type) { + let Result:! type; + fn Op[self: Self](x: T) -> Result; +} + +interface RefBind(T:! type) { + let Result:! type; + fn Op[self: Self](p: T*) -> Result*; +} +``` + +However, this results in the type of a binding depending on the what +[category](/docs/design/values.md#expression-categories) the expression to the +left of the dot has. This could change the interpretation of code using +[indexing](/docs/design/expressions/indexing.md), such as an expression like +`a[b].F()`, when the type of `a` is changed from or to a checked generic. This +is because the the expression is legal as long as the type of `a` implements +`IndexWith(typeof(b))`, but category of `a[b]` depends on whether the type of +`a` is known to implement `IndirectIndexWith(typeof(b))`. + +To avoid this problem, we make the result type of the binding the same whether +value or reference binding is used. See +[this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/99fb69aaaa24bd502d71cd4259f37cfa8346b244#r1597317217). From 201afe09bddb5a9daea881904608eb444c25c808 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 14 May 2024 20:11:34 +0000 Subject: [PATCH 65/77] inlined call compiler intrinsic --- proposals/p3720.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index b4f16e54403ac..0197cfcfe8fe9 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -330,7 +330,7 @@ impl __Bind_C_Static as Call(()) with .Result = i32 { // Other implementations of `Call(())` are the same. fn Op[unused self: Self]() -> i32 { // Calls `C.Static()`, without triggering binding again. - return call_compiler_intrinsic( + return inlined_call_compiler_intrinsic( , ()); } } @@ -352,24 +352,24 @@ interface, using the implementations just defined. Note: ```carbon v.F() == (v as __Bind_C_F)() == (v as __Bind_C_F).((__Bind_C_F as Call(())).Op)() - == method_call_compiler_intrinsic( + == inlined_method_call_compiler_intrinsic( , v as __Bind_C_F, ()); - == method_call_compiler_intrinsic( + == inlined_method_call_compiler_intrinsic( , (v as __Bind_C_F) as C, ()) - == method_call_compiler_intrinsic( + == inlined_method_call_compiler_intrinsic( , v, ()) r.F() == (*(&r as __Bind_C_F*))() == (*(&r as __Bind_C_F*)).((__Bind_C_F as Call(())).Op)() - == method_call_compiler_intrinsic( + == inlined_method_call_compiler_intrinsic( , *(&r as __Bind_C_F*) , ()); - == method_call_compiler_intrinsic( + == inlined_method_call_compiler_intrinsic( , *(&r as __Bind_C_F*) as C, ()) - == method_call_compiler_intrinsic( + == inlined_method_call_compiler_intrinsic( , r , ()) ``` @@ -731,7 +731,7 @@ Results in this implementation: ```carbon impl __Bind_I_F(C) as Call(()) where .Result = () { fn Op[self: Self]() { - inline_method_call_compiler_intrinsic( + inlined_method_call_compiler_intrinsic( , self as C, ()); } } From bd9a0d49806716a69dd764153c8abe682836752f Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 15 May 2024 21:18:41 +0000 Subject: [PATCH 66/77] Checkpoint progress. --- proposals/p3720.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 0197cfcfe8fe9..861055f630682 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -77,7 +77,7 @@ callable object. We would like a model that allows `x.M` to be meaningful in a way that is consistent with the existing meaning of `x.f` and generalizes well across different kinds of methods and callables. -The other issue is how we clearly delineate the `self` associated with a method +Another issue is how we clearly delineate the `self` associated with a method signature as separate from the `self` of function values. ## Background @@ -246,7 +246,7 @@ How does this arise? > across multiple statements, as in: > > ```carbon -> let M:! auto = C.Static +> let M:! auto = C.Static; > v.(M)(); > r.(M)(); > ``` @@ -558,10 +558,10 @@ the names of an unbound class member. There are a two cases: methods and fields. #### Methods -Restricting to value methods, since mutating (`addr self`) methods are out of -scope for this proposal, the receiver object may be passed by value. To be able -to call the method, we must include a restriction that the result of `ValueBind` -implements `Call(())`: +Restricting to value methods, since mutating (`addr self`) methods are +[out of scope for this proposal](#background), the receiver object may be passed +by value. To be able to call the method, we must include a restriction that the +result of `ValueBind` implements `Call(())`: ```carbon // `m` can be any method object that implements `Call(())` once bound. @@ -1014,7 +1014,7 @@ This proposal says `c.Increment` is a reference expression with a type that adapts `Counter`. For `c.Increment()` to affect the value of `c.count`, there needs to be some way for the `Call` operator to mutate `c`. The current definition of `Call` takes `self` by value, though, so this doesn't work. -Addressing this is out of scope of the current proposal. +Addressing this is [out of scope of the current proposal](#background). We could instead make `c.Increment` be a value holding `&c`. That would allow `Call` to work even when taking `self` by value. This is the solution likely From f435a9e1104b9e0ab453c92e3fbf39f5f0f2de4d Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 15 May 2024 21:49:49 +0000 Subject: [PATCH 67/77] Checkpoint progress. --- proposals/p3720.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 861055f630682..e019994caf3c8 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -285,7 +285,7 @@ impl __TypeOf_C_F as RefBind(C) > [non-instance interface members](#non-instance-interface-members). Those implementations are how we get from `__C_F` with type `__TypeOf_C_F` to -`v as __Bind_C_F` or `&r as __Bind_C_F*`: +`v as __Bind_C_F` or `&r as __Bind_C_F*`, conceptually following these steps: ```carbon // `v` is a value and so uses `ValueBind` @@ -301,6 +301,28 @@ r.F() == r.(C.F)() == (*(&r as __Bind_C_F*))() ``` +However, to avoid recursive application of these same rules, we need to avoid +expressing this in terms of evaluating `__C_F.(`...`)`. Instead the third step +shall instead use an intrinsic compiler primitive, as in: + +```carbon +// `v` is a value and so uses `ValueBind` +v.F() == v.(C.F)() + == v.(__C_F)() + == inlined_method_call_compiler_intrinsic( + , + __C_F, (v))() + == (v as __Bind_C_F)() + +// `r` is a reference expression and so uses `RefBind` +r.F() == r.(C.F)() + == r.(__C_F)() + == (*inlined_method_call_compiler_intrinsic( + , + __C_F, (&r)))() + == (*(&r as __Bind_C_F*))() +``` + At this point we have resolved the binding, and are left with an expression of type `__Bind_C_F` followed by `()`. In the first case, that expression is a value expression. In the second case, it is a reference expression. From ed7eb8ab374ab1e50901359c4baba5144abd26b5 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 15 May 2024 22:06:51 +0000 Subject: [PATCH 68/77] Checkpoint progress. --- proposals/p3720.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index e019994caf3c8..4e4dea691ee33 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -366,7 +366,8 @@ call is rewritten to an invocation of the `Op` method of the `Call(())` interface, using the implementations just defined. Note: - Passing `*(&r as __Bind_C_F*)` to the `self` parameter of `Call(()).Op` - converts the reference expression to a value. + converts the reference expression to a value. Note that mutating + (`addr self`) methods are [out of scope for this proposal](#background). - The `Call` interface is special. We don't [rewrite](#instance-interface-members) calls to `Call(__).Op` to avoid infinite recursion. From fa7e22f8caa43a9706417aa8de34160d6d86abcc Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 16 May 2024 15:36:57 +0000 Subject: [PATCH 69/77] Checkpoint progress. --- proposals/p3720.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 4e4dea691ee33..95f3d18072dd8 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -909,7 +909,13 @@ support for properties with binding is how the customization would be done. The most natural way to support this customization would be to have multiple interfaces. The compiler would try them in a specified order and use the one it found first. This has the downside of the possibility of different behavior in a -checked generic context where only some of the implementations are visible. +checked generic context where only some of the implementations are visible. Our +choice to +[make the result type the same `Result` associated type of the `Bind` interface](#proposal) +independent of whether the `ValueBind` or `RefBind` interface is used makes this +less concerning. Only the phase of the result, not the type, would depend on +which implementations were found, similar to +[how indexing works](/docs/design/expressions/indexing.md). ### Future: building block for language features such as API extension From 976b0b15b5aeebfdc692851027bf505dda3c707d Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 17 May 2024 20:38:11 +0000 Subject: [PATCH 70/77] BindToValue --- proposals/p3720.md | 274 +++++++++++++++++++++++---------------------- 1 file changed, 138 insertions(+), 136 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 95f3d18072dd8..31f7af8b34d81 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -36,7 +36,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [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) - [Non-instance members are idempotent under binding](#non-instance-members-are-idempotent-under-binding) - - [Separate `Result` types for `ValueBind` and `RefBind`](#separate-result-types-for-valuebind-and-refbind) + - [Separate `Result` types for `BindToValue` and `BindToRef`](#separate-result-types-for-bindtovalue-and-bindtoref) @@ -121,7 +121,7 @@ is a value expression, a reference expression, or a facet: ```carbon // This determines the type of the result of binding. It is a -// separate interface shared by `ValueBind` and `RefBind` to +// separate interface shared by `BindToValue` and `BindToRef` to // ensure they produce the same result type. We don't want the // type of an expression to depend on the expression category // of the arguments. @@ -130,31 +130,31 @@ interface Bind(T:! type) { } // For a value expression `x` with type `T` and an expression -// `y` of type `U`, `x.(y)` is `y.((U as ValueBind(T)).Op)(x)` -interface ValueBind(T:! type) { +// `y` of type `U`, `x.(y)` is `y.((U as BindToValue(T)).Op)(x)` +interface BindToValue(T:! type) { extend Bind(T); fn Op[self: Self](x: T) -> Result; } // For a reference expression `x` using a binding `var x: T` // and an expression `y` of type `U`, `x.(y)` is -// `*y.((U as RefBind(T)).Op)(&x)` -interface RefBind(T:! type) { +// `*y.((U as BindToRef(T)).Op)(&x)` +interface BindToRef(T:! type) { extend Bind(T); fn Op[self: Self](p: T*) -> Result*; } // For a facet value, which includes all type values, `T` and // an expression `y` of type `U`, `T.(y)` is -// `y.((U as TypeBind(T)).Op)()`. -interface TypeBind(T:! type) { +// `y.((U as BindToType(T)).Op)()`. +interface BindToType(T:! type) { let Result:! type; fn Op[self: Self]() -> Result; } ``` -> **Note:** `TypeBind` is its own binding interface since the members of a type -> are defined by their values, not by their type. +> **Note:** `BindToType` is its own binding interface since the members of a +> type are defined by their values, not by their type. The other member access operators -- `x.y`, `x->y`, and `x->(y)` -- are defined by how they rewrite into the `x.(y)` form using these two rules: @@ -189,7 +189,7 @@ the type of binding that member with either a `C` value or variable. ```carbon class __TypeOf_C_F {} let __C_F:! __TypeOf_C_F = {}; -class __Bind_C_F { +class __Binding_C_F { adapt C; } @@ -215,11 +215,11 @@ is interpreted as: ```carbon let v: C = {.x = 3}; -Assert((v as __Bind_C_F).(Call(()).Op)() == 8); -Assert((v as __Bind_C_Static).(Call(()).Op)() == 2); +Assert((v as __Binding_C_F).(Call(()).Op)() == 8); +Assert((v as __Binding_C_Static).(Call(()).Op)() == 2); var r: C = {.x = 4}; -Assert((r as __Bind_C_F).(Call(()).Op)() == 9); -Assert((r as __Bind_C_Static).(Call(()).Op)() == 2); +Assert((r as __Binding_C_F).(Call(()).Op)() == 9); +Assert((r as __Binding_C_Static).(Call(()).Op)() == 2); ``` How does this arise? @@ -258,47 +258,48 @@ How does this arise? > binding. The binding operators are defined using three dedicated interfaces -- -`ValueBind`, `RefBind`, and `TypeBind` -- +`BindToValue`, `BindToRef`, and `BindToType` -- [as defined in the "proposal" section](#proposal). These binding operations are implemented for the types of the class members: ```carbon -impl __TypeOf_C_F as ValueBind(C) - where .Result = __Bind_C_F { - fn Op[unused self: Self](x: C) -> __Bind_C_F { - return x as __Bind_C_F; +impl __TypeOf_C_F as BindToValue(C) + where .Result = __Binding_C_F { + fn Op[unused self: Self](x: C) -> __Binding_C_F { + return x as __Binding_C_F; } } // Note that the `Result` type has to match, since // it is an associated type in the `Bind(C)` interface -// that both `ValueBind(C)` and `RefBind(C)` extend. -impl __TypeOf_C_F as RefBind(C) - where .Result = __Bind_C_F { - fn Op[unused self: Self](p: C*) -> __Bind_C_F* { - return p as __Bind_C_F*; +// that both `BindToValue(C)` and `BindToRef(C)` extend. +impl __TypeOf_C_F as BindToRef(C) + where .Result = __Binding_C_F { + fn Op[unused self: Self](p: C*) -> __Binding_C_F* { + return p as __Binding_C_F*; } } ``` -> **Note:** `TypeBind` is used for +> **Note:** `BindToType` is used for > [non-instance interface members](#non-instance-interface-members). Those implementations are how we get from `__C_F` with type `__TypeOf_C_F` to -`v as __Bind_C_F` or `&r as __Bind_C_F*`, conceptually following these steps: +`v as __Binding_C_F` or `&r as __Binding_C_F*`, conceptually following these +steps: ```carbon -// `v` is a value and so uses `ValueBind` +// `v` is a value and so uses `BindToValue` v.F() == v.(C.F)() == v.(__C_F)() - == __C_F.((__TypeOf_C_F as ValueBind(C)).Op)(v)() - == (v as __Bind_C_F)() + == __C_F.((__TypeOf_C_F as BindToValue(C)).Op)(v)() + == (v as __Binding_C_F)() -// `r` is a reference expression and so uses `RefBind` +// `r` is a reference expression and so uses `BindToRef` r.F() == r.(C.F)() == r.(__C_F)() - == (*__C_F.((__TypeOf_C_F as RefBind(C)).Op)(&r))() - == (*(&r as __Bind_C_F*))() + == (*__C_F.((__TypeOf_C_F as BindToRef(C)).Op)(&r))() + == (*(&r as __Binding_C_F*))() ``` However, to avoid recursive application of these same rules, we need to avoid @@ -306,25 +307,25 @@ expressing this in terms of evaluating `__C_F.(`...`)`. Instead the third step shall instead use an intrinsic compiler primitive, as in: ```carbon -// `v` is a value and so uses `ValueBind` +// `v` is a value and so uses `BindToValue` v.F() == v.(C.F)() == v.(__C_F)() == inlined_method_call_compiler_intrinsic( - , + , __C_F, (v))() - == (v as __Bind_C_F)() + == (v as __Binding_C_F)() -// `r` is a reference expression and so uses `RefBind` +// `r` is a reference expression and so uses `BindToRef` r.F() == r.(C.F)() == r.(__C_F)() == (*inlined_method_call_compiler_intrinsic( - , + , __C_F, (&r)))() - == (*(&r as __Bind_C_F*))() + == (*(&r as __Binding_C_F*))() ``` At this point we have resolved the binding, and are left with an expression of -type `__Bind_C_F` followed by `()`. In the first case, that expression is a +type `__Binding_C_F` followed by `()`. In the first case, that expression is a value expression. In the second case, it is a reference expression. The last ingredient is the implementation of the call interfaces for these bound @@ -332,10 +333,10 @@ types. ```carbon // Binding with `C.F` produces something with type -// `__Bind_C_F` whether it is a value or reference +// `__Binding_C_F` whether it is a value or reference // expression. Since `C.F` takes `self: Self` it can be // used in both cases. -impl __Bind_C_F as Call(()) with .Result = i32 { +impl __Binding_C_F as Call(()) with .Result = i32 { fn Op[self: Self]() -> i32 { // Calls `(self as C).(C.F)()`, but without triggering // binding again. @@ -348,7 +349,7 @@ impl __Bind_C_F as Call(()) with .Result = i32 { // implements the call interfaces on `__TypeOf_C_Static`. // This allows `C.Static()` to work, in addition to // `v.Static()` and `r.Static()`. -impl __Bind_C_Static as Call(()) with .Result = i32 { +impl __Binding_C_Static as Call(()) with .Result = i32 { // Other implementations of `Call(())` are the same. fn Op[unused self: Self]() -> i32 { // Calls `C.Static()`, without triggering binding again. @@ -365,7 +366,7 @@ call. As described in call is rewritten to an invocation of the `Op` method of the `Call(())` interface, using the implementations just defined. Note: -- Passing `*(&r as __Bind_C_F*)` to the `self` parameter of `Call(()).Op` +- Passing `*(&r as __Binding_C_F*)` to the `self` parameter of `Call(()).Op` converts the reference expression to a value. Note that mutating (`addr self`) methods are [out of scope for this proposal](#background). - The `Call` interface is special. We don't @@ -373,25 +374,25 @@ interface, using the implementations just defined. Note: infinite recursion. ```carbon -v.F() == (v as __Bind_C_F)() - == (v as __Bind_C_F).((__Bind_C_F as Call(())).Op)() +v.F() == (v as __Binding_C_F)() + == (v as __Binding_C_F).((__Binding_C_F as Call(())).Op)() == inlined_method_call_compiler_intrinsic( - , - v as __Bind_C_F, ()); + , + v as __Binding_C_F, ()); == inlined_method_call_compiler_intrinsic( , - (v as __Bind_C_F) as C, ()) + (v as __Binding_C_F) as C, ()) == inlined_method_call_compiler_intrinsic( , v, ()) -r.F() == (*(&r as __Bind_C_F*))() - == (*(&r as __Bind_C_F*)).((__Bind_C_F as Call(())).Op)() +r.F() == (*(&r as __Binding_C_F*))() + == (*(&r as __Binding_C_F*)).((__Binding_C_F as Call(())).Op)() == inlined_method_call_compiler_intrinsic( - , - *(&r as __Bind_C_F*) , ()); + , + *(&r as __Binding_C_F*) , ()); == inlined_method_call_compiler_intrinsic( , - *(&r as __Bind_C_F*) as C, ()) + *(&r as __Binding_C_F*) as C, ()) == inlined_method_call_compiler_intrinsic( , r , ()) @@ -424,18 +425,18 @@ To allow this to work, we need the implementation of the bind interfaces to allow implicit conversions: ```carbon -impl [T:! ImplicitAs(B)] __TypeOf_B_F as ValueBind(T) - where .Result = __Bind_B_F { - fn Op[self: Self](x: T) -> __Bind_B_F { - return (x as B) as __Bind_B_F; +impl [T:! ImplicitAs(B)] __TypeOf_B_F as BindToValue(T) + where .Result = __Binding_B_F { + fn Op[self: Self](x: T) -> __Binding_B_F { + return (x as B) as __Binding_B_F; } } impl [T:! type where .Self* impls ImplicitAs(B*)] - __TypeOf_B_F as RefBind(T) - where .Result = __Bind_B_F { - fn Op[self: Self](p: T*) -> __Bind_B_F* { - return (p as B*) as __Bind_B_F*; + __TypeOf_B_F as BindToRef(T) + where .Result = __Binding_B_F { + fn Op[self: Self](p: T*) -> __Binding_B_F* { + return (p as B*) as __Binding_B_F*; } } ``` @@ -469,14 +470,14 @@ results in an implementation using `Different` instead of `C`: ```carbon // `C.G` will only bind to values that can implicitly convert // to type `Different`. -impl [T:! ImplicitAs(Different)] __TypeOf_C_G as ValueBind(T) - where .Result = __Bind_C_G; +impl [T:! ImplicitAs(Different)] __TypeOf_C_G as BindToValue(T) + where .Result = __Binding_C_G; ``` ### Data fields -The same `ValueBind` and `RefBind` operations allow us to define access to the -data fields in an object, without any additional changes. +The same `BindToValue` and `BindToRef` operations allow us to define access to +the data fields in an object, without any additional changes. For example, given a class with a data member `m` with type `i32`: @@ -504,14 +505,14 @@ binding operation. However, this time the result type of binding is simply class __TypeOf_C_m {} let __C_m:! __TypeOf_C_m = {}; -impl __TypeOf_C_m as ValueBind(C) where .Result = i32 { +impl __TypeOf_C_m as BindToValue(C) where .Result = i32 { fn Op[self: Self](x: C) -> i32 { // Effectively performs `x.m`, but without triggering binding again. return value_compiler_intrinsic(x, __OffsetOf_C_m, i32) } } -impl __TypeOf_C_m as RefBind(C) where .Result = i32 { +impl __TypeOf_C_m as BindToRef(C) where .Result = i32 { fn Op[self: Self](p: C*) -> i32* { // Effectively performs `&p->m`, but without triggering binding again, // by doing something like `((p as byte*) + __OffsetOf_C_m) as i32*` @@ -524,18 +525,18 @@ These definitions give us the desired semantics: ```carbon // For value `v` with type `T` and `y` of type `U`, -// `v.(y)` is `y.((U as ValueBind(T)).Op)(v)` +// `v.(y)` is `y.((U as BindToValue(T)).Op)(v)` v.m == v.(C.m) == v.(__C_m) - == v.(__C_m as (__TypeOf_C_m as ValueBind(C))) - == __C_m.((__TypeOf_C_m as ValueBind(C)).Op)(v) + == v.(__C_m as (__TypeOf_C_m as BindToValue(C))) + == __C_m.((__TypeOf_C_m as BindToValue(C)).Op)(v) == value_compiler_intrinsic(v, __OffsetOf_C_m, i32) // For reference expression `var x: T` and `y` of type `U`, -// `x.(y)` is `*y.(U as RefBind(T)).Op(&x)` +// `x.(y)` is `*y.(U as BindToRef(T)).Op(&x)` x.m == x.(C.m) == x.(__C_m) - == *__C_m.((__TypeOf_C_m as RefBind(C)).Op)(&x) + == *__C_m.((__TypeOf_C_m as BindToRef(C)).Op)(&x) == *offset_compiler_intrinsic(&x, __OffsetOf_C_m, i32) // Note that this requires `x` to be a reference expression, // so `&x` is valid, and produces a reference expression, @@ -584,14 +585,14 @@ the names of an unbound class member. There are a two cases: methods and fields. Restricting to value methods, since mutating (`addr self`) methods are [out of scope for this proposal](#background), the receiver object may be passed by value. To be able to call the method, we must include a restriction that the -result of `ValueBind` implements `Call(())`: +result of `BindToValue` implements `Call(())`: ```carbon // `m` can be any method object that implements `Call(())` once bound. fn CallMethod - [T:! type, M:! ValueBind(T) where .Result impls Call(())] + [T:! type, M:! BindToValue(T) where .Result impls Call(())] (x: T, m: M) -> auto { - // `x.(m)` is rewritten to a call to `ValueBind(T).Op`. The + // `x.(m)` is rewritten to a call to `BindToValue(T).Op`. The // constraint on `M` ensures the result implements `Call(())`. return x.(m)(); } @@ -634,21 +635,21 @@ Fields can be accessed, given the type of the field ```carbon fn GetField - [T:! type, F:! ValueBind(T) where .Result = i32] + [T:! type, F:! BindToValue(T) where .Result = i32] (x: T, f: F) -> i32 { - // `x.(f)` is rewritten to `f.((F as ValueBind(T)).Op)(x)`, - // and `(F as ValueBind(T)).Op` is a method on `f` with + // `x.(f)` is rewritten to `f.((F as BindToValue(T)).Op)(x)`, + // and `(F as BindToValue(T)).Op` is a method on `f` with // return type `i32` by the constraint on `F`. return x.(f); } fn SetField - [T:! type, F:! RefBind(T) where .Result = i32] + [T:! type, F:! BindToRef(T) where .Result = i32] (x: T*, f: F, y: i32) { // `x->(f)` is rewritten to `(*x).(f)`, which then - // becomes: `*f.((F as RefBind(T)).Op)(&*x)` + // becomes: `*f.((F as BindToRef(T)).Op)(&*x)` // The constraint `F` says the return type of - // `(F as RefBind(T)).Op` is `i32*`, which is + // `(F as BindToRef(T)).Op` is `i32*`, which is // dereferenced to get an `i32` reference expression // which may then be assigned. x->(f) = y; @@ -722,17 +723,18 @@ class __TypeOf_I_F {} let __I_F:! __TypeOf_I_F = {}; ``` -That type implements `ValueBind` for any type that implements the interface `I`: +That type implements `BindToValue` for any type that implements the interface +`I`: ```carbon -class __Bind_I_F(T:! I) { +class __Binding_I_F(T:! I) { adapt T; } -impl forall [T:! I] __TypeOf_I_F as ValueBind(T) - where .Result = __Bind_I_F(T) { - fn Op[self: Self](x: T) -> __Bind_I_F(T) { - // Valid since `__Bind_I_F(T)` adapts `T`: - return x as __Bind_I_F(T); +impl forall [T:! I] __TypeOf_I_F as BindToValue(T) + where .Result = __Binding_I_F(T) { + fn Op[self: Self](x: T) -> __Binding_I_F(T) { + // Valid since `__Binding_I_F(T)` adapts `T`: + return x as __Binding_I_F(T); } } ``` @@ -752,7 +754,7 @@ impl C as I { Results in this implementation: ```carbon -impl __Bind_I_F(C) as Call(()) where .Result = () { +impl __Binding_I_F(C) as Call(()) where .Result = () { fn Op[self: Self]() { inlined_method_call_compiler_intrinsic( , self as C, ()); @@ -764,9 +766,9 @@ A call such as `c.(I.F)()` goes through these rewrites: ```carbon c.(I.F)() == c.(__I_F)() - == __I_F.((__TypeOf_I_F as ValueBind(C)).Op)(c)() - == (c as __Bind_I_F(C))() - == (c as __Bind_I_F(C)).((__Bind_I_F(C) as Call(())).Op)() + == __I_F.((__TypeOf_I_F as BindToValue(C)).Op)(c)() + == (c as __Binding_I_F(C))() + == (c as __Binding_I_F(C)).((__Binding_I_F(C) as Call(())).Op)() ``` Which results in invoking the above implementation that will ultimately call @@ -777,7 +779,7 @@ Which results in invoking the above implementation that will ultimately call ### Non-instance interface members -Non-instance members use the `TypeBind` interface instead. For example, if `G` +Non-instance members use the `BindToType` interface instead. For example, if `G` is a non-instance function of an interface `J`: ```carbon @@ -794,14 +796,14 @@ class __TypeOf_J_G {} let __J_G:! __TypeOf_J_G = {}; ``` -Since this is a non-instance member, this type implements `TypeBind` instead of -`ValueBind`: +Since this is a non-instance member, this type implements `BindToType` instead +of `BindToValue`: ```carbon -class __TypeBind_J_G(T:! J) {} -impl forall [T:! J] __TypeOf_J_G as TypeBind(T) - where .Result = __TypeBind_J_G(T) { - fn Op[self: Self]() -> __TypeBind_J_G(T) { +class __TypeBinding_J_G(T:! J) {} +impl forall [T:! J] __TypeOf_J_G as BindToType(T) + where .Result = __TypeBinding_J_G(T) { + fn Op[self: Self]() -> __TypeBinding_J_G(T) { return {}; } } @@ -820,7 +822,7 @@ impl C as J { Results in this implementation: ```carbon -impl __TypeBind_J_G(C) as Call(()) where .Result = () { +impl __TypeBinding_J_G(C) as Call(()) where .Result = () { fn Op[self: Self]() { Fireworks(); } @@ -831,16 +833,16 @@ A call such as `C.(J.G)()` goes through these rewrites: ```carbon C.(J.G)() == C.(__J_G)() - == __J_G.((__TypeOf_J_G as TypeBind(C)).Op)()() - == ({} as __TypeBind_J_G(C))() - == (({} as __TypeBind_J_G(C)) as Call(())).Op() + == __J_G.((__TypeOf_J_G as BindToType(C)).Op)()() + == ({} as __TypeBinding_J_G(C))() + == (({} as __TypeBinding_J_G(C)) as Call(())).Op() ``` Which calls the above implementation that calls `Fireworks()`. -> **Note:** Binding for non-instance members doesn't work with `ValueBind`, we -> need `TypeBind`. Otherwise there is no way to get the value `C` into the -> result type. Furthermore, we want `TypeBind` implementation no matter which +> **Note:** Binding for non-instance members doesn't work with `BindToValue`, we +> need `BindToType`. Otherwise there is no way to get the value `C` into the +> result type. Furthermore, we want `BindToType` implementation no matter which > facet of the type is used in the code. ### C++ operator overloading @@ -912,9 +914,9 @@ found first. This has the downside of the possibility of different behavior in a checked generic context where only some of the implementations are visible. Our choice to [make the result type the same `Result` associated type of the `Bind` interface](#proposal) -independent of whether the `ValueBind` or `RefBind` interface is used makes this -less concerning. Only the phase of the result, not the type, would depend on -which implementations were found, similar to +independent of whether the `BindToValue` or `BindToRef` interface is used makes +this less concerning. Only the phase of the result, not the type, would depend +on which implementations were found, similar to [how indexing works](/docs/design/expressions/indexing.md). ### Future: building block for language features such as API extension @@ -961,15 +963,15 @@ consistent with other operators. > > ```carbon > // For value `x` with type `T` and `y` of type `U`, -> // `x.(y)` is `x.((T as ValueBind(U)).Op)(y)` -> interface ValueBind(U:! type) { +> // `x.(y)` is `x.((T as BindToValue(U)).Op)(y)` +> interface BindToValue(U:! type) { > let Result:! type; > fn Op[self: Self](x: U) -> Result; > } > > // For reference expression `var x: T` and `y` of type `U`, -> // `x.(y)` is `*x.((T as RefBind(U)).Op)(y)` -> interface RefBind(U:! type) { +> // `x.(y)` is `*x.((T as BindToRef(U)).Op)(y)` +> interface BindToRef(U:! type) { > let Result:! type; > fn Op[addr self: Self*](x: U) -> Result*; > } @@ -987,9 +989,9 @@ approach, each method type is constrained: ```carbon // `m1`, `m2`, and `m3` are methods on class `T`. fn Call3Methods[T:! type, - M1:! ValueBind(T) where .Result impls Call(()), - M2:! ValueBind(T) where .Result impls Call(()), - M3:! ValueBind(T) where .Result impls Call(())] + M1:! BindToValue(T) where .Result impls Call(()), + M2:! BindToValue(T) where .Result impls Call(()), + M3:! BindToValue(T) where .Result impls Call(())] (x: T, m1: M1, m2: M2, m3: M3) -> auto; ``` @@ -1002,10 +1004,10 @@ deduced types would be written in a different order: > // `m1`, `m2`, and `m3` are methods on class `T`. > fn Call3MethodsAlternative1 > [M1:! type, M2:! type, M3:! type, -> T:! ValueBind(M1) & ValueBind(M2) & ValueBind(M3) -> where .(ValueBind(M1).Result) impls Call(()) -> and .(ValueBind(M2).Result) impls Call(()) -> and .(ValueBind(M3).Result) impls Call(())] +> T:! BindToValue(M1) & BindToValue(M2) & BindToValue(M3) +> where .(BindToValue(M1).Result) impls Call(()) +> and .(BindToValue(M2).Result) impls Call(()) +> and .(BindToValue(M3).Result) impls Call(())] > (x: T, m1: M1, m2: M2, m3: M3) -> auto; > ``` @@ -1018,9 +1020,9 @@ length: > // `m1`, `m2`, and `m3` are methods on class `T`. > fn Call3MethodsAlternative2 > [T:! type, -> M1:! type where T impls (ValueBind(.Self) where .Result impls Call(())), -> M2:! type where T impls (ValueBind(.Self) where .Result impls Call(())), -> M3:! type where T impls (ValueBind(.Self) where .Result impls Call(()))] +> M1:! type where T impls (BindToValue(.Self) where .Result impls Call(())), +> M2:! type where T impls (BindToValue(.Self) where .Result impls Call(())), +> M3:! type where T impls (BindToValue(.Self) where .Result impls Call(()))] > (x: T, m1: M1, m2: M2, m3: M3) -> auto; > ``` @@ -1094,19 +1096,19 @@ is translated into something like: > class __TypeOf_C_Static {} > let __C_Static:! __TypeOf_C_Static = {}; > -> class __Bind_C_Static { +> class __Binding_C_Static { > adapt C; > } > -> impl __TypeOf_C_Static as ValueBind(C) -> where .Result = __Bind_C_Static; +> impl __TypeOf_C_Static as BindToValue(C) +> where .Result = __Binding_C_Static; > -> impl __TypeOf_C_Static as RefBind(C) -> where .Result = __Bind_C_Static; +> impl __TypeOf_C_Static as BindToRef(C) +> where .Result = __Binding_C_Static; > ``` An alternative is that binding of a non-instance member is idempotent, so there -is no `__Bind_C_Static` type and `ValueBind(C)` results in a value of type +is no `__Binding_C_Static` type and `BindToValue(C)` results in a value of type `__TypeOf_C_Static` instead: > **Alternative:** @@ -1116,9 +1118,9 @@ is no `__Bind_C_Static` type and `ValueBind(C)` results in a value of type > // Might need to be a `var` instead? > let __C_Static:! __TypeOf_C_Static = {}; > -> impl __TypeOf_C_Static as ValueBind(C) +> impl __TypeOf_C_Static as BindToValue(C) > where .Result = __TypeOf_C_Static; -> impl __TypeOf_C_Static as RefBind(C) +> impl __TypeOf_C_Static as BindToRef(C) > where .Result = __TypeOf_C_Static; > ``` @@ -1134,18 +1136,18 @@ There are a few concerns with this alternative: This was discussed in [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files#r1513681915). -### Separate `Result` types for `ValueBind` and `RefBind` +### Separate `Result` types for `BindToValue` and `BindToRef` An earlier iteration of this proposal had separate `Result` associated types for -`ValueBind` and `RefBind`, as in: +`BindToValue` and `BindToRef`, as in: ```carbon -interface ValueBind(T:! type) { +interface BindToValue(T:! type) { let Result:! type; fn Op[self: Self](x: T) -> Result; } -interface RefBind(T:! type) { +interface BindToRef(T:! type) { let Result:! type; fn Op[self: Self](p: T*) -> Result*; } From 57c35e9ad5658317871b156dc9d7938285ffba2c Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 21 May 2024 20:59:31 +0000 Subject: [PATCH 71/77] Checkpoint progress. --- proposals/p3720.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index 31f7af8b34d81..cef46a7770c71 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -37,6 +37,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Separate interface for compile-time binding instead of type binding](#separate-interface-for-compile-time-binding-instead-of-type-binding) - [Non-instance members are idempotent under binding](#non-instance-members-are-idempotent-under-binding) - [Separate `Result` types for `BindToValue` and `BindToRef`](#separate-result-types-for-bindtovalue-and-bindtoref) + - [`BindToValue` is a subtype of `BindToRef`](#bindtovalue-is-a-subtype-of-bindtoref) + - [Directly rewrite all calls to interface member functions to method call intrinsics](#directly-rewrite-all-calls-to-interface-member-functions-to-method-call-intrinsics) @@ -154,7 +156,10 @@ interface BindToType(T:! type) { ``` > **Note:** `BindToType` is its own binding interface since the members of a -> type are defined by their values, not by their type. +> type are defined by their values, not by their type. Observe that this means +> that a generic function might not use `BindToType` on a symbolic value that +> was not known to be a facet, where it would use `BindToType` on the concrete +> value. The other member access operators -- `x.y`, `x->y`, and `x->(y)` -- are defined by how they rewrite into the `x.(y)` form using these two rules: @@ -1165,3 +1170,13 @@ is because the the expression is legal as long as the type of `a` implements To avoid this problem, we make the result type of the binding the same whether value or reference binding is used. See [this comment on #3720](https://github.com/carbon-language/carbon-lang/pull/3720/files/99fb69aaaa24bd502d71cd4259f37cfa8346b244#r1597317217). + +### `BindToValue` is a subtype of `BindToRef` + +FIXME: +https://github.com/carbon-language/carbon-lang/pull/3720#discussion_r1597317217 + +### Directly rewrite all calls to interface member functions to method call intrinsics + +FIXME: +https://github.com/carbon-language/carbon-lang/pull/3720#discussion_r1597324225 From 7ca75ad2edd2e039792f8ff2226c27e086e1f7da Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 22 May 2024 17:18:04 +0000 Subject: [PATCH 72/77] Checkpoint progress. --- proposals/p3720.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/p3720.md b/proposals/p3720.md index cef46a7770c71..eb8a906852b8a 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -44,6 +44,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Abstract +FIXME: switch to "member binding" terminology + 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. From 21fa97470956c864d4bfde45cb908340566a8015 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 23 May 2024 20:40:00 +0000 Subject: [PATCH 73/77] bind -> member bind --- proposals/p3720.md | 191 +++++++++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 93 deletions(-) diff --git a/proposals/p3720.md b/proposals/p3720.md index eb8a906852b8a..2ea3a652ea940 100644 --- a/proposals/p3720.md +++ b/proposals/p3720.md @@ -1,4 +1,4 @@ -# Binding operators +# Member binding operators