mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 17:51:07 +01:00
Update member_access.md to reflect accepted proposals (#3160)
Most changes are due to proposal #2360, but this also includes changes to reflect: #1136, #2138, #2006, #2550, and #2964. --------- Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
@@ -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
|
||||
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:
|
||||
|
||||
When the first operand is not a package or namespace name, there are three
|
||||
remaining cases we wish to support:
|
||||
|
||||
- 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.
|
||||
|
||||
Note that because a type is a value, and a type-of-type is a type, these cases
|
||||
are overlapping and not entirely separable.
|
||||
|
||||
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.
|
||||
|
||||
**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).
|
||||
|
||||
For a simple member access, the word is looked up in the following types:
|
||||
|
||||
- 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.
|
||||
|
||||
The results of these lookups are [combined](#lookup-ambiguity).
|
||||
|
||||
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.
|
||||
|
||||
For example:
|
||||
```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 = {};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Types and facets
|
||||
|
||||
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).
|
||||
|
||||
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:
|
||||
|
||||
- `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.
|
||||
|
||||
Unlike the previous case, both simple and compound member access is allowed.
|
||||
|
||||
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.
|
||||
|
||||
```carbon
|
||||
interface Cowboy {
|
||||
fn Draw[self: Self]();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
fn G[template T:! type](x: TemplateWrapper(T)) -> T {
|
||||
// 🤷 Not yet decided.
|
||||
return x.field;
|
||||
}
|
||||
```
|
||||
|
||||
> **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 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).
|
||||
|
||||
**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
|
||||
resolution.
|
||||
|
||||
```carbon
|
||||
class Cowboy { fn Draw[self: Self](); }
|
||||
interface Renderable {
|
||||
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 DrawChecked[T:! Renderable](c: T) {
|
||||
// `Draw` resolves to `Renderable.Draw`.
|
||||
c.Draw();
|
||||
}
|
||||
|
||||
fn Draw(c: Cowboy) {
|
||||
// ✅ Calls member of `Cowboy`.
|
||||
DrawDirect(c);
|
||||
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`.
|
||||
DrawGeneric(c);
|
||||
// ❌ Error: ambiguous.
|
||||
DrawChecked(c);
|
||||
// In contrast to this which calls member of `Cowboy`:
|
||||
c.Draw();
|
||||
}
|
||||
```
|
||||
|
||||
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();
|
||||
}
|
||||
```
|
||||
|
||||
> **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 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
|
||||
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](); }
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// ✅ OK, lookup in type and lookup in type-of-type find the same entity.
|
||||
DrawTemplate(s);
|
||||
fn Draw(c: Cowboy, p: Pig, r: RoundWidget, s: SquareWidget) {
|
||||
// ❌ Error: ambiguous. `Cowboy.Draw` and
|
||||
// `(Cowboy as Renderable).Draw` are different.
|
||||
DrawTemplate2(c);
|
||||
|
||||
// ✅ OK, found in type.
|
||||
r.Draw();
|
||||
s.Draw();
|
||||
// ✅ 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 `SquareWidget` and lookup in facet
|
||||
// type `SquareWidget as Renderable` find the same entity.
|
||||
DrawTemplate2(s);
|
||||
|
||||
// ❌ Error: `Fly` method not found in `Pig` or
|
||||
// `Pig as type`.
|
||||
FlyTemplate(p);
|
||||
}
|
||||
```
|
||||
|
||||
#### Lookup ambiguity
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
`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 for simple member access
|
||||
|
||||
For a simple member access `a.b` where `b` names a member of an interface `I`:
|
||||
|
||||
- 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 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 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`, `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]();
|
||||
}
|
||||
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))();
|
||||
impl i32 as Printable;
|
||||
|
||||
// ✅ 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.
|
||||
fn MemberAccess(n: i32) {
|
||||
// ✅ 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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user