diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index c3fb04292db52..6b4c7f85e9ac7 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -13,10 +13,14 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Overview](#overview) - [Member resolution](#member-resolution) - [Package and namespace members](#package-and-namespace-members) - - [Lookup within values](#lookup-within-values) - - [Templates and generics](#templates-and-generics) - - [Lookup ambiguity](#lookup-ambiguity) + - [Types and facets](#types-and-facets) + - [Values](#values) + - [Facet binding](#facet-binding) + - [Compile-time bindings](#compile-time-bindings) + - [Lookup ambiguity](#lookup-ambiguity) - [`impl` lookup](#impl-lookup) + - [`impl` lookup for simple member access](#impl-lookup-for-simple-member-access) + - [`impl` lookup for compound member access](#impl-lookup-for-compound-member-access) - [Instance binding](#instance-binding) - [Non-instance members](#non-instance-members) - [Non-vacuous member access restriction](#non-vacuous-member-access-restriction) @@ -58,17 +62,19 @@ Compound member accesses allow specifying a qualified member name. For example: ```carbon -package Widgets api; -interface Widget { +namespace Widgets; + +interface Widgets.Widget { fn Grow[addr self: Self*](factor: f64); } -class Cog { + +class Widgets.Cog { var size: i32; fn Make(size: i32) -> Self; extend impl as Widgets.Widget; } -fn GrowSomeCogs() { +fn Widgets.GrowSomeCogs() { var cog1: Cog = Cog.Make(1); var cog2: Cog = cog1.Make(2); var cog_pointer: Cog* = &cog2; @@ -110,6 +116,19 @@ A member access expression is processed using the following steps: The process of _member resolution_ determines which member `M` a member access expression is referring to. +For a simple member access, the second operand is a word. If the first operand +is a type, facet, package, or namespace, a search for the word is performed in +the first operand. Otherwise, a search for the word is performed in the type of +the first operand. In either case, the search must succeed. In the latter case, +if the result is an instance member, then [instance binding](#instance-binding) +is performed on the first operand. + +For a compound member access, the second operand is evaluated as a compile-time +constant to determine the member being accessed. The evaluation is required to +succeed and to result in a member of a type, interface, or non-type facet. If +the result is an instance member, then [instance binding](#instance-binding) is +always performed on the first operand. + ### Package and namespace members If the first operand is a package or namespace name, the expression must be a @@ -136,57 +155,84 @@ fn CallMyFunction2() { } ``` -### Lookup within values - -When the first operand is not a package or namespace name, there are three -remaining cases we wish to support: +The first operand may also be the keyword `package`, as in `package.Foo`, to +name the `Foo` member of the current package. This can be used to disambiguate +between different `Foo` definitions, as in: -- The first operand is a value, and lookup should consider members of the - value's type. -- The first operand is a type, and lookup should consider members of that - type. For example, `i32.Least` should find the member constant `Least` of - the type `i32`. -- The first operand is a type-of-type, and lookup should consider members of - that type-of-type. For example, `Addable.Add` should find the member - function `Add` of the interface `Addable`. Because a type-of-type is a type, - this is a special case of the previous bullet. +```carbon +// This defines `package.Foo` +class Foo {} +class Bar { + // This defines `Bar.Foo`, or equivalently `package.Bar.Foo`. + class Foo {} + fn F() { + // ✅ OK, `x` has type `Foo` from the outer scope. + var x: package.Foo = {}; + + // ❌ Error: ambiguous; + // `Foo` could mean `package.Foo` or `Bar.Foo`. + var y: Foo = {}; + } +} +``` -Note that because a type is a value, and a type-of-type is a type, these cases -are overlapping and not entirely separable. +### Types and facets -If any of the above lookups ever looks for members of a type parameter, it -should consider members of the type-of-type, treating the type parameter as an -archetype. +If the first operand is a type or facet, it must be a compile-time constant. +This disallows member access into a type except during compile-time, see leads +issue [#1293](https://github.com/carbon-language/carbon-lang/issues/1293). -**Note:** If lookup is performed into a type that involves a template parameter, -the lookup will be performed both in the context of the template definition and -in the context of the template instantiation, as described in -[templates and generics](#templates-and-generics). +Like the previous case, types (including +[facet types](/docs/design/generics/terminology.md#facet-type)) have member +names, and lookup searches those names. For example: -For a simple member access, the word is looked up in the following types: +- `i32.Least` finds the member constant `Least` of the type `i32`. +- `Add.Op` finds the member function `Op` of the interface `Add`. Because a + facet type is a type, this is a special case of the previous bullet. -- If the first operand can be evaluated and evaluates to a type, that type. -- If the type of the first operand can be evaluated, that type. -- If the type of the first operand is a generic type parameter, and the type - of that generic type parameter can be evaluated, that type-of-type. +Unlike the previous case, both simple and compound member access is allowed. -The results of these lookups are [combined](#lookup-ambiguity). +Non-type facets, such as `T as Cowboy`, also have members. Specifically, the +members of the `impl` or `impl`s that form the implementation of `T as Cowboy`. +Being part of the `impl` rather than the interface, no further +[`impl` lookup](#impl-lookup) is needed. -For a compound member access, the second operand is evaluated as a constant to -determine the member being accessed. The evaluation is required to succeed and -to result in a member of a type or interface. +```carbon +interface Cowboy { + fn Draw[self: Self](); +} -For example: +interface Renderable { + fn Draw[self: Self](); +} +class Avatar { + extend impl Avatar as Cowboy; + extend impl Avatar as Renderable; +} ``` + +Simple member access `(Avatar as Cowboy).Draw` finds the `Cowboy.Draw` +implementation for `Avatar`, ignoring `Renderable.Draw`. + +### Values + +If the first operand is not a type, package, namespace, or facet it does not +have member names, and a search is performed into the type of the first operand +instead. + +```carbon interface Printable { fn Print[self: Self](); } + impl i32 as Printable; + class Point { var x: i32; var y: i32; - // Internal impl injects the name `Print` into class `Point`. + // Extending impl injects the name `Print` into + // class `Point`. extend impl as Printable; } @@ -200,27 +246,49 @@ fn PrintPointTwice() { // ✅ OK, `Print` found in type of `p`, namely `Point`. p.Print(); - // ✅ OK, `Print` found in the type `Printable`. + // ✅ OK, `Print` found in the type `Printable`, and + // `Printable.Print` found in the type of `p`. p.(Printable.Print)(); } +``` + +### Facet binding + +If any of the above lookups would search for members of a +[facet binding](/docs/design/generics/terminology.md#facet-binding) `T:! C`, it +searches the facet `T as C` instead, treating the facet binding as an +[archetype](/docs/design/generics/terminology.md#archetype). + +For example: + +``` +interface Printable { + fn Print[self: Self](); +} + fn GenericPrint[T:! Printable](a: T) { - // ✅ OK, type of `a` is the type parameter `T`; - // `Print` found in the type of `T`, namely `Printable`. + // ✅ OK, type of `a` is the facet binding `T`; + // `Print` found in the facet `T as Printable`. a.Print(); } -fn CallGenericPrint(p: Point) { - GenericPrint(p); -} ``` -#### Templates and generics +**Note:** If lookup is performed into a type that involves a template binding, +the lookup will be performed both in the context of the template definition and +in the context of the template instantiation, as described in +[the "compile-time bindings" section](#compile-time-bindings). The results of +these lookups are [combined](#lookup-ambiguity). -If the value or type of the first operand depends on a template or generic -parameter, the lookup is performed from a context where the value of that -parameter is unknown. Evaluation of an expression involving the parameter may -still succeed, but will result in a symbolic value involving that parameter. +#### Compile-time bindings -``` +If the value or type of the first operand depends on a checked or template +generic parameter, or in fact any +[compile-time binding](/docs/design/generics/terminology.md#bindings), the +lookup is performed from a context where the value of that binding is unknown. +Evaluation of an expression involving the binding may still succeed, but will +result in a symbolic value involving that binding. + +```carbon class GenericWrapper(T:! type) { var field: T; } @@ -229,48 +297,98 @@ fn F[T:! type](x: GenericWrapper(T)) -> T { return x.field; } -class TemplateWrapper(template T:! type) { - var field: T; +interface Renderable { + fn Draw[self: Self](); } -fn G[template T:! type](x: TemplateWrapper(T)) -> T { - // 🤷 Not yet decided. - return x.field; +fn DrawChecked[T:! Renderable](c: T) { + // `Draw` resolves to `Renderable.Draw`. + c.Draw(); +} + +class Cowboy { fn Draw[self: Self](); } +impl Cowboy as Renderable { fn Draw[self: Self](); } + +fn CallsDrawChecked(c: Cowboy) { + // ✅ Calls member of `impl Cowboy as Renderable`. + DrawChecked(c); + // In contrast to this which calls member of `Cowboy`: + c.Draw(); } ``` -> **TODO:** The behavior of `G` above is not yet fully decided. If class -> templates can be specialized, then we cannot know the members of -> `TemplateWrapper(T)` without knowing `T`, so this first lookup will find -> nothing. In any case, as described below, the lookup will be performed again -> when `T` is known. +If the value or type depends on any template bindings, the lookup is redone from +a context where the values of those bindings are known, but where the values of +any symbolic bindings are still unknown. The lookup results from these two +contexts are [combined](#lookup-ambiguity). + +```carbon +fn DrawTemplate[template T:! type](c: T) { + // `Draw` not found in `type`, looked up in the + // actual deduced value of `T`. + c.Draw(); +} + +fn CallsDrawTemplate(c: Cowboy) { + // ✅ Calls member of `Cowboy`: + DrawTemplate(c); + // Same behavior as: + c.Draw(); +} +``` -If the value or type depends on any template parameters, the lookup is redone -from a context where the values of those parameters are known, but where the -values of any generic parameters are still unknown. The lookup results from -these two contexts are [combined](#lookup-ambiguity). +> **TODO:** The behavior of this code depends on whether we decide to allow +> class templates to be specialized: +> +> ```carbon +> class TemplateWrapper(template T:! type) { +> var field: T; +> } +> fn G[template T:! type](x: TemplateWrapper(T)) -> T { +> // 🤷 Not yet decided. +> return x.field; +> } +> ``` +> +> If class specialization is allowed, then we cannot know the members of +> `TemplateWrapper(T)` without knowing `T`, so this first lookup will find +> nothing. In any case, the lookup will be performed again when `T` is known. -**Note:** All lookups are done from a context where the values of any generic -parameters that are in scope are unknown. Unlike for a template parameter, the -actual value of a generic parameter never affects the result of member +**Note:** All lookups are done from a context where the values of any symbolic +bindings that are in scope are unknown. Unlike for a template binding, the +actual value of a symbolic binding never affects the result of member resolution. +##### Lookup ambiguity + +Multiple lookups can be performed when resolving a member access expression with +a [template binding](#compile-time-bindings). We resolve this the same way as +when looking in multiple interfaces that are +[combined with `&`](/docs/design/generics/details.md#combining-interfaces-by-anding-type-of-types): + +- If more than one distinct member is found, after performing + [`impl` lookup](#impl-lookup) if necessary, the lookup is ambiguous, and the + program is invalid. +- If no members are found, the program is invalid. +- Otherwise, the result of combining the lookup results is the unique member + that was found. + ```carbon -class Cowboy { fn Draw[self: Self](); } interface Renderable { fn Draw[self: Self](); } + +fn DrawTemplate2[template T:! Renderable](c: T) { + // Member lookup finds `Renderable.Draw` and the `Draw` + // member of the actual deduced value of `T`, if any. + c.Draw(); +} + +class Cowboy { fn Draw[self: Self](); } impl Cowboy as Renderable { fn Draw[self: Self](); } -fn DrawDirect(c: Cowboy) { c.Draw(); } -fn DrawGeneric[T:! Renderable](c: T) { c.Draw(); } -fn DrawTemplate[template T:! Renderable](c: T) { c.Draw(); } -fn Draw(c: Cowboy) { - // ✅ Calls member of `Cowboy`. - DrawDirect(c); - // ✅ Calls member of `impl Cowboy as Renderable`. - DrawGeneric(c); - // ❌ Error: ambiguous. - DrawTemplate(c); +class Pig { } +impl Pig as Renderable { + fn Draw[self: Self](); } class RoundWidget { @@ -287,35 +405,68 @@ class SquareWidget { } } -fn DrawWidget(r: RoundWidget, s: SquareWidget) { - // ✅ OK, lookup in type and lookup in type-of-type find the same entity. - DrawTemplate(r); +fn FlyTemplate[template T:! type](c: T) { + c.Fly(); +} + +fn Draw(c: Cowboy, p: Pig, r: RoundWidget, s: SquareWidget) { + // ❌ Error: ambiguous. `Cowboy.Draw` and + // `(Cowboy as Renderable).Draw` are different. + DrawTemplate2(c); + + // ✅ OK, lookup in type `Pig` finds nothing, so uses + // lookup in facet type `Pig as Renderable`. + DrawTemplate2(p); + + // ✅ OK, lookup in type `RoundWidget` and lookup in facet + // type `RoundWidget as Renderable` find the same entity. + DrawTemplate2(r); - // ✅ OK, lookup in type and lookup in type-of-type find the same entity. - DrawTemplate(s); + // ✅ OK, lookup in type `SquareWidget` and lookup in facet + // type `SquareWidget as Renderable` find the same entity. + DrawTemplate2(s); - // ✅ OK, found in type. - r.Draw(); - s.Draw(); + // ❌ Error: `Fly` method not found in `Pig` or + // `Pig as type`. + FlyTemplate(p); } ``` -#### Lookup ambiguity +## `impl` lookup -Multiple lookups can be performed when resolving a member access expression. If -more than one member is found, after performing [`impl` lookup](#impl-lookup) if -necessary, the lookup is ambiguous, and the program is invalid. Similarly, if no -members are found, the program is invalid. Otherwise, the result of combining -the lookup results is the unique member that was found. +`impl` lookup maps a member of an interface to the corresponding member of the +relevant `impl`. It is performed when member access names an interface member, +except when the member was found by a search of a facet type scope in a simple +member access expression. -## `impl` lookup +### `impl` lookup for simple member access + +For a simple member access `a.b` where `b` names a member of an interface `I`: -When the second operand of a member access expression resolves to a member of an -interface `I`, and the first operand is a value other than a type-of-type, -_`impl` lookup_ is performed to map the member of the interface to the -corresponding member of the relevant `impl`. The member of the `impl` replaces -the member of the interface in all further processing of the member access -expression. +- If the interface member was found by searching a + non-[facet-type](/docs/design/generics/terminology.md#facet-type) scope `T`, + for example a class or an adapter, then `impl` lookup is performed for + `T as I`. + - In the case where the member was found in a base class of the class that + was searched, `T` is the derived class that was searched, not the base + class in which the name was declared. + - More generally, if the member was found in something the type extends, + such as a facet type or mixin, `T` is the type that was initially + searched, not what it extended. +- Otherwise, `impl` lookup is not performed. + +The appropriate `impl T as I` implementation is located. The program is invalid +if no such `impl` exists. When `T` or `I` depends on a symbolic binding, a +suitable constraint must be specified to ensure that such an `impl` will exist. +When `T` or `I` depends on a template binding, this check is deferred until the +value for the template binding is known. + +`M` is replaced by the member of the `impl` that corresponds to `M`. + +[Instance binding](#instance-binding) may also apply if the member is an +instance member. + +For example: ```carbon interface Addable { @@ -325,6 +476,7 @@ interface Addable { default fn Sum[Seq:! Iterable where .ValueType = Self](seq: Seq) -> Self { // ... } + alias AliasForSum = Sum; } class Integer { @@ -334,94 +486,27 @@ class Integer { // #4, generated from default implementation for #2. // fn Sum[...](...); } -} -fn SumIntegers(v: Vector(Integer)) -> Integer { - // Member resolution resolves the name `Sum` to #2. - // `impl` lookup then locates the `impl Integer as Addable`, - // and determines that the member access refers to #4, - // which is then called. - return Integer.Sum(v); -} - -fn AddTwoIntegers(a: Integer, b: Integer) -> Integer { - // Member resolution resolves the name `Add` to #1. - // `impl` lookup then locates the `impl Integer as Addable`, - // and determines that the member access refers to #3. - // Finally, instance binding will be performed as described later. - // This can be written more verbosely and explicitly as any of: - // - `return a.(Integer.Add)(b);` - // - `return a.(Addable.Add)(b);` - // - `return a.(Integer.(Addable.Add))(b);` - return a.Add(b); + alias AliasForAdd = Addable.Add; } ``` -The type `T` that is expected to implement `I` depends on the first operand of -the member access expression, `V`: - -- If `V` can be evaluated and evaluates to a type, then `T` is `V`. - ```carbon - // `V` is `Integer`. `T` is `V`, which is `Integer`. - // Alias refers to #2. - alias AddIntegers = Integer.Add; - ``` -- Otherwise, `T` is the type of `V`. - ```carbon - let a: Integer = {}; - // `V` is `a`. `T` is the type of `V`, which is `Integer`. - // `a.Add` refers to #2. - let twice_a: Integer = a.Add(a); - ``` - -The appropriate `impl T as I` implementation is located. The program is invalid -if no such `impl` exists. When `T` or `I` depends on a generic parameter, a -suitable constraint must be specified to ensure that such an `impl` will exist. -When `T` or `I` depends on a template parameter, this check is deferred until -the argument for the template parameter is known. - -`M` is replaced by the member of the `impl` that corresponds to `M`. - -```carbon -interface I { - // #1 - default fn F[self: Self]() {} - let N:! i32; -} -class C { - extend impl as I where .N = 5 { - // #2 - fn F[self: C]() {} - } -} - -// `V` is `I` and `M` is `I.F`. Because `V` is a type-of-type, -// `impl` lookup is not performed, and the alias binds to #1. -alias A1 = I.F; - -// `V` is `C` and `M` is `I.F`. Because `V` is a type, `impl` -// lookup is performed with `T` being `C`, and the alias binds to #2. -alias A2 = C.F; - -let c: C = {}; - -// `V` is `c` and `M` is `I.N`. Because `V` is a non-type, `impl` -// lookup is performed with `T` being the type of `c`, namely `C`, and -// `M` becomes the associated constant from `impl C as I`. -// The value of `Z` is 5. -let Z: i32 = c.N; -``` - -[Instance binding](#instance-binding) may also apply if the member is an -instance member. - -```carbon -var c: C; -// `V` is `c` and `M` is `I.F`. Because `V` is not a type, `T` is the -// type of `c`, which is `C`. `impl` lookup is performed, and `M` is -// replaced with #2. Then instance binding is performed. -c.F(); -``` +- For `Integer.Sum`, member resolution resolves the name `Sum` to \#2, which + is not an instance member. `impl` lookup then locates the + `impl Integer as Addable`, and determines that the member access refers to + \#4. +- For `i.Add(j)` where `i: Integer`, member resolution resolves the name `Add` + to \#1, which is an instance member. `impl` lookup then locates the + `impl Integer as Addable`, and determines that the member access refers to + \#3. Finally, instance binding will be performed as described later. +- `Integer.AliasForAdd` finds \#3, the `Add` member of the facet type + `Integer as Addable`, not \#1, the interface member `Addable.Add`. +- `i.AliasForAdd`, with `i: Integer`, finds \#3, the `Add` member of the facet + type `Integer as Addable`, and performs + [instance binding](#instance-binding) since the member is an instance + member. +- `Addable.AliasForSum` finds \#2, the member in the interface `Addable`, and + does not perform `impl` lookup. **Note:** When an interface member is added to a class by an alias, `impl` lookup is not performed as part of handling the alias, but will happen when @@ -429,21 +514,21 @@ naming the interface member as a member of the class. ```carbon interface Renderable { - // #1 + // #5 fn Draw[self: Self](); } class RoundWidget { impl as Renderable { - // #2 + // #6 fn Draw[self: Self](); } - // `Draw` names the member of the `Renderable` interface. + // `Draw` names #5, the member of the `Renderable` interface. alias Draw = Renderable.Draw; } class SquareWidget { - // #3 + // #7 fn Draw[self: Self]() {} impl as Renderable { alias Draw = Self.Draw; @@ -452,35 +537,36 @@ class SquareWidget { fn DrawWidget(r: RoundWidget, s: SquareWidget) { // ✅ OK: In the inner member access, the name `Draw` resolves to the - // member `Draw` of `Renderable`, #1, which `impl` lookup replaces with - // the member `Draw` of `impl RoundWidget as Renderable`, #2. + // member `Draw` of `Renderable`, #5, which `impl` lookup replaces with + // the member `Draw` of `impl RoundWidget as Renderable`, #6. // The outer member access then forms a bound member function that - // calls #2 on `r`, as described in "Instance binding". + // calls #6 on `r`, as described in "Instance binding". r.(RoundWidget.Draw)(); // ✅ OK: In the inner member access, the name `Draw` resolves to the - // member `Draw` of `SquareWidget`, #3. + // member `Draw` of `SquareWidget`, #7. // The outer member access then forms a bound member function that - // calls #3 on `s`. + // calls #7 on `s`. s.(SquareWidget.Draw)(); // ❌ Error: In the inner member access, the name `Draw` resolves to the - // member `Draw` of `SquareWidget`, #3. + // member `Draw` of `SquareWidget`, #7. // The outer member access fails because we can't call - // #3, `Draw[self: SquareWidget]()`, on a `RoundWidget` object `r`. + // #7, `Draw[self: SquareWidget]()`, on a `RoundWidget` object `r`. r.(SquareWidget.Draw)(); // ❌ Error: In the inner member access, the name `Draw` resolves to the - // member `Draw` of `Renderable`, #1, which `impl` lookup replaces with - // the member `Draw` of `impl RoundWidget as Renderable`, #2. + // member `Draw` of `Renderable`, #5, which `impl` lookup replaces with + // the member `Draw` of `impl RoundWidget as Renderable`, #6. // The outer member access fails because we can't call - // #2, `Draw[self: RoundWidget]()`, on a `SquareWidget` object `s`. + // #6, `Draw[self: RoundWidget]()`, on a `SquareWidget` object `s`. s.(RoundWidget.Draw)(); } base class WidgetBase { // ✅ OK, even though `WidgetBase` does not implement `Renderable`. alias Draw = Renderable.Draw; + fn DrawAll[T:! Renderable](v: Vector(T)) { for (var w: T in v) { // ✅ OK. Unqualified lookup for `Draw` finds alias `WidgetBase.Draw` @@ -490,6 +576,7 @@ base class WidgetBase { // `Renderable`. Finally, the member function is bound to `w` as // described in "Instance binding". w.(Draw)(); + // ❌ Error: `Self.Draw` performs `impl` lookup, which fails // because `WidgetBase` does not implement `Renderable`. w.(Self.Draw)(); @@ -509,22 +596,93 @@ fn DrawTriangle(t: TriangleWidget) { } ``` +### `impl` lookup for compound member access + +For a compound member access `a.(b)` where `b` names a member of an interface +`I`, `impl` lookup is performed for `T as I`, where: + +- If `b` is an instance member, `T` is the type of `a`. In this case, + [instance binding](#instance-binding) is always performed. +- Otherwise, `a` is implicitly converted to `I`, and `T` is the result of + symbolically evaluating the converted expression. In this case, + [instance binding](#instance-binding) is never performed. + +For example: + +```carbon +fn AddTwoIntegers(a: Integer, b: Integer) -> Integer { + // Since `Addable.Add` is an instance member of `Addable`, `T` + // is set to the type of `a`, and so uses `Integer as Addable`. + return a.(Addable.Add)(b); + // ^ impl lookup and instance binding here + // Impl lookup transforms this into #3: + // return a.((Integer as Addable).Add)(b); + // which no longer requires impl lookup. + + // ❌ By the same logic, in this example, `T` is set to the + // type of `Integer`, and so uses `type as Addable`, which + // isn't implemented. + return Integer.(Addable.Add)(...); +} + +fn SumIntegers(v: Vector(Integer)) -> Integer { + // Since `Addable.Sum` is a non-instance member of `Addable`, + // `Integer` is implicitly converted to `Addable`, and so uses + // `Integer as Addable`. + Integer.(Addable.Sum)(v); + // ^ impl lookup but no instance binding here + // Impl lookup transforms this into #4: + // ((Integer as Addable).Sum)(v); + // which no longer requires impl lookup. + + var a: Integer; + // ❌ This is an error since `a` does not implicitly convert to + // a type. + a.(Addable.Sum)(v); +} +``` + ## Instance binding -If member resolution and `impl` lookup produce a member `M` that is an instance -member -- that is, a field or a method -- and the first operand `V` of `.` is a -value other than a type, then _instance binding_ is performed, as follows: +Next, _instance binding_ may be performed. This associates an expression with a +particular object instance. For example, this is the value bound to `self` when +calling a method. + +For the simple member access syntax `x.y`, if `x` is an entity that has member +names, such as a namespace or a type, then `y` is looked up within `x`, and +instance binding is not performed. Otherwise, `y` is looked up within the type +of `x` and instance binding is performed if an instance member is found. + +If instance binding is performed: -- For a field member in class `C`, `V` is required to be of type `C` or of a - type derived from `C`. The result is the corresponding subobject within `V`. - The result is an lvalue if `V` is an lvalue. +- For a field member in class `C`, `x` is required to be of type `C` or of a + type derived from `C`. The result is the corresponding subobject within `x`. + If `x` is an + [initializing expression](/docs/design/values.md#initializing-expressions), + then a + [temporary is materialized](/docs/design/values.md#temporary-materialization) + for `x`. The result of `x.y` has the same + [expression category](/docs/design/values.md#expression-categories) as the + possibly materialized `x`. ```carbon - var dims: auto = {.width = 1, .height = 2}; + class Size { + var width: i32; + var height: i32; + } + + var dims: Size = {.width = 1, .height = 2}; // `dims.width` denotes the field `width` of the object `dims`. Print(dims.width); - // `dims` is an lvalue, so `dims.height` is an lvalue. + // `dims` is a reference expression, so `dims.height` is a + // reference expression. dims.height = 3; + + fn GetSize() -> Size; + // `GetSize()` returns an initializing expression, which is + // materialized as a temporary on member access, so + // `GetSize().width` is an ephemeral reference expression. + Print(GetSize().width); ``` - For a method, the result is a _bound method_, which is a value `F` such that @@ -532,8 +690,8 @@ value other than a type, then _instance binding_ is performed, as follows: `self` parameter initialized by a corresponding recipient argument: - If the method declares its `self` parameter with `addr`, the recipient - argument is `&V`. - - Otherwise, the recipient argument is `V`. + argument is `&x`. + - Otherwise, the recipient argument is `x`. ```carbon class Blob { @@ -551,11 +709,41 @@ value other than a type, then _instance binding_ is performed, as follows: } ``` +The compound member access syntax `x.(Y)`, where `Y` names an instance member, +always performs instance binding. It is an error if `Y` is already bound to an +instance member. For example: + +```carbon +interface DebugPrint { + // instance member + fn Print[self:Self](); +} +impl i32 as DebugPrint; +impl type as DebugPrint; + +fn Debug() { + var i: i32 = 1; + + // Prints `1` using `(i32 as DebugPrint).Print` bound to `i`. + i.(DebugPrint.Print)(); + + // Prints `i32` using `(type as DebugPrint).Print` bound to `i32`. + i32.(DebugPrint.Print)(); + + // ❌ This is an error since `i32.(DebugPrint.Print)` is already + // bound, and may not be bound again to `i`. + i.(i32.(DebugPrint.Print))(); +} +``` + +To get the `M` member of interface `I` for a type `T`, use `(T as I).M`, as this +doesn't attempt to perform instance binding on `T`, in contrast to `T.(I.M)`. + ## Non-instance members If instance binding is not performed, the result is the member `M` determined by member resolution and `impl` lookup. Evaluating the member access expression -evaluates `V` and discards the result. +evaluates the first argument and discards the result. An expression that names an instance member, but for which instance binding is not performed, can only be used as the second operand of a compound member @@ -571,11 +759,12 @@ fn CallStaticMethod(c: C) { // ✅ OK, calls `C.StaticMethod`. C.StaticMethod(); - // ✅ OK, evaluates expression `c` then calls `C.StaticMethod`. + // ✅ OK, evaluates expression `c`, discards the result, then + // calls `C.StaticMethod`. c.StaticMethod(); - // ❌ Error: name of instance member `C.field` can only be used in a - // member access or alias. + // ❌ Error: name of instance member `C.field` can only be used in + // a member access or alias. C.field = 1; // ✅ OK, instance binding is performed by outer member access, // same as `c.field = 1;` @@ -600,30 +789,34 @@ always used for lookup. interface Printable { fn Print[self: Self](); } -impl i32 as Printable { - fn Print[self: Self](); -} +impl i32 as Printable; + fn MemberAccess(n: i32) { - // ✅ OK: `Printable.Print` is the interface member. - // `i32.(Printable.Print)` is the corresponding member of the `impl`. - // `n.(i32.(Printable.Print))` is a bound member function naming that member. - n.(i32.(Printable.Print))(); - - // ✅ Same as above, `n.(Printable.Print)` is effectively interpreted as - // `n.(T.(Printable.Print))()`, where `T` is the type of `n`, - // because `n` does not evaluate to a type. Performs impl lookup - // and then instance binding. + // ✅ OK: `(i32 as Printable).Print` is the `Print` member of the + // `i32 as Printable` facet corresponding to the `Printable.Print` + // interface member. + // `n.((i32 as Printable).Print)` is that member function bound to `n`. + n.((i32 as Printable).Print)(); + + // ✅ Same as above, `n.(Printable.Print)` is effectively interpreted + // as `n.((T as Printable).Print)()`, where `T` is the type of `n`. + // Performs impl lookup and then instance binding. n.(Printable.Print)(); } -// ✅ OK, member `Print` of interface `Printable`. -alias X1 = Printable.Print; -// ❌ Error, compound access doesn't perform impl lookup or instance binding. -alias X2 = Printable.(Printable.Print); -// ✅ OK, member `Print` of `impl i32 as Printable`. -alias X3 = i32.(Printable.Print); -// ❌ Error, compound access doesn't perform impl lookup or instance binding. -alias X4 = i32.(i32.(Printable.Print)); +interface Factory { + fn Make() -> Self; +} +impl i32 as Factory; + +// ✅ OK, member `Make` of interface `Factory`. +alias X1 = Factory.Make; +// ❌ Error, compound access without impl lookup or instance binding. +alias X2 = Factory.(Factory.Make); +// ✅ OK, member `Make` of `impl i32 as Factory`. +alias X3 = (i32 as Factory).Make; +// ❌ Error, compound access without impl lookup or instance binding. +alias X4 = i32.((i32 as Factory).Make); ``` ## Precedence and associativity @@ -672,3 +865,7 @@ var n: i32 = 1 + X.Y; - Proposal [#989: member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) - [Question for leads: constrained template name lookup](https://github.com/carbon-language/carbon-lang/issues/949) +- Proposal + [#2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) +- Proposal + [#2550: Simplified package declaration for the `Main` package](https://github.com/carbon-language/carbon-lang/pull/2550) diff --git a/proposals/p3162.md b/proposals/p3162.md index c0c6429b6f4d9..22d912f8d9135 100644 --- a/proposals/p3162.md +++ b/proposals/p3162.md @@ -113,8 +113,6 @@ these changes: - [Language design overview](/docs/design/README.md) - [Generics terminology](/docs/design/generics/terminology.md) - [Member access expressions](/docs/design/expressions/member_access.md) - **FIXME:** Need to sync with - [#3162](https://github.com/carbon-language/carbon-lang/pull/3162). Some of these changes have already been implemented in: