소스 검색

Member access expressions (#989)

Support for member access expressions with syntax `container.member`, covering cases such as:

* `object.field`
* `object.method(args)`
* `package.namespace.class.member`
* `object.(interface.member)`
* `object.(class.member)`

... and so on. Includes the rule for template name lookup as decided in #949.

Co-authored-by: josh11b <josh11b@users.noreply.github.com>
Richard Smith 4 년 전
부모
커밋
18b423dc5f
3개의 변경된 파일1084개의 추가작업 그리고 1개의 파일을 삭제
  1. 85 1
      docs/design/expressions/README.md
  2. 653 0
      docs/design/expressions/member_access.md
  3. 346 0
      proposals/p0989.md

+ 85 - 1
docs/design/expressions/README.md

@@ -12,6 +12,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 -   [Overview](#overview)
 -   [Precedence](#precedence)
+-   [Names](#names)
+    -   [Unqualified names](#unqualified-names)
+    -   [Qualified names and member access](#qualified-names-and-member-access)
 -   [Operators](#operators)
 -   [Conversions and casts](#conversions-and-casts)
 -   [`if` expressions](#if-expressions)
@@ -54,6 +57,13 @@ graph BT
     braces["{...}"]
     click braces "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#literals"
 
+    unqualifiedName["x"]
+    click unqualifiedName "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/README.md#unqualified-names"
+
+    memberAccess>"x.y<br>
+                    x.(...)"]
+    click memberAccess "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/member_access.md"
+
     as["x as T"]
     click as "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/implicit_conversions.md"
 
@@ -79,7 +89,8 @@ graph BT
 
     expressionEnd["x;"]
 
-    as & not --> parens & braces
+    memberAccess --> parens & braces & unqualifiedName
+    as & not --> memberAccess
     comparison --> as
     and & or --> comparison & not
     if & expressionEnd --> and & or
@@ -113,6 +124,79 @@ The diagram's attributes are:
     -   For example, `+` and `-` are left-associative and in the same precedence
         group, so `a + b + c - d` is treated as `((a + b) + c) - d`.
 
+## Names
+
+### Unqualified names
+
+An _unqualified name_ is a [word](../lexical_conventions/words.md) that is not a
+keyword and is not preceded by a period (`.`).
+
+**TODO:** Name lookup rules for unqualified names.
+
+### Qualified names and member access
+
+A _qualified name_ is a word that is prefixed by a period. Qualified names
+appear in the following contexts:
+
+-   [Designators](/docs/design/classes.md#literals): `.` _word_
+-   [Direct member access expressions](member_access.md): _expression_ `.`
+    _word_
+
+```
+var x: auto = {.hello = 1, .world = 2};
+                ^^^^^       ^^^^^ qualified name
+               ^^^^^^      ^^^^^^ designator
+
+x.hello = x.world;
+  ^^^^^     ^^^^^ qualified name
+^^^^^^^   ^^^^^^^ member access expression
+```
+
+Qualified names refer to members of an entity determined by the context in which
+the expression appears. For a member access, the entity is named by the
+expression preceding the period. In a struct literal, the entity is the struct
+type. For example:
+
+```
+package Foo api;
+namespace N;
+fn N.F() {}
+
+fn G() {
+  // Same as `(Foo.N).F()`.
+  // `Foo.N` names namespace `N` in package `Foo`.
+  // `(Foo.N).F` names function `F` in namespace `N`.
+  Foo.N.F();
+}
+
+// `.n` refers to the member `n` of `{.n: i32}`.
+fn H(a: {.n: i32}) -> i32 {
+  // `a.n` is resolved to the member `{.n: i32}.n`,
+  // and names the corresponding subobject of `a`.
+  return a.n;
+}
+
+fn J() {
+  // `.n` refers to the member `n of `{.n: i32}`.
+  H({.n = 5 as i32});
+}
+```
+
+Member access expressions associate left-to-right. If the member name is more
+complex than a single _word_, an indirect member access expression can be used,
+with parentheses around the member name:
+
+-   _expression_ `.` `(` _member-access-expression_ `)`
+
+```
+interface I { fn F[me: Self](); }
+class X {}
+external impl X as I { fn F[me: Self]() {} }
+
+// `x.I.F()` would mean `(x.I).F()`.
+fn Q(x: X) { x.(I.F)(); }
+```
+
 ## Operators
 
 Most expressions are modeled as operators:

+ 653 - 0
docs/design/expressions/member_access.md

@@ -0,0 +1,653 @@
+# Qualified names and member access
+
+<!--
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+Exceptions. See /LICENSE for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+-->
+
+<!-- toc -->
+
+## Table of contents
+
+-   [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)
+-   [`impl` lookup](#impl-lookup)
+-   [Instance binding](#instance-binding)
+-   [Non-instance members](#non-instance-members)
+-   [Non-vacuous member access restriction](#non-vacuous-member-access-restriction)
+-   [Precedence and associativity](#precedence-and-associativity)
+-   [Alternatives considered](#alternatives-considered)
+-   [References](#references)
+
+<!-- tocstop -->
+
+## Overview
+
+A _qualified name_ is a [word](../lexical_conventions/words.md) that is preceded
+by a period. The name is found within a contextually determined entity:
+
+-   In a member access expression, this is the entity preceding the period.
+-   For a designator in a struct literal, the name is introduced as a member of
+    the struct type.
+
+A _member access expression_ allows a member of a value, type, interface,
+namespace, and so on to be accessed by specifying a qualified name for the
+member.
+
+A member access expression is either a _direct_ member access expression of the
+form:
+
+-   _member-access-expression_ ::= _expression_ `.` _word_
+
+or an _indirect_ member access of the form:
+
+-   _member-access-expression_ ::= _expression_ `.` `(` _expression_ `)`
+
+For example:
+
+```carbon
+package Widgets api;
+interface Widget {
+  fn Grow[addr me: Self*](factor: f64);
+}
+class Cog {
+  var size: i32;
+  fn Make(size: i32) -> Self;
+  impl as Widgets.Widget;
+}
+
+fn GrowSomeCogs() {
+  var cog1: Cog = Cog.Make(1);
+  var cog2: Cog = cog1.Make(2);
+  let cog1_size: i32 = cog1.size;
+  cog1.Grow(1.5);
+  cog2.(Cog.Grow)(cog1_size as f64);
+  cog1.(Widget.Grow)(1.1);
+  cog2.(Widgets.Cog.(Widgets.Widget.Grow))(1.9);
+}
+```
+
+A member access expression is processed using the following steps:
+
+-   First, the word or parenthesized expression to the right of the `.` is
+    [resolved](#member-resolution) to a specific member entity, called `M` in
+    this document.
+-   Then, if necessary, [`impl` lookup](#impl-lookup) is performed to map from a
+    member of an interface to a member of the relevant `impl`, potentially
+    updating `M`.
+-   Then, if necessary, [instance binding](#instance-binding) is performed to
+    locate the member subobject corresponding to a field name or to build a
+    bound method object, producing the result of the member access expression.
+-   If [instance binding is not performed](#non-instance-members), the result is
+    `M`.
+
+## Member resolution
+
+The process of _member resolution_ determines which member `M` a member access
+expression is referring to.
+
+### Package and namespace members
+
+If the first operand is a package or namespace name, the member access must be
+direct. The _word_ must name a member of that package or namespace, and the
+result is the package or namespace member with that name.
+
+An expression that names a package or namespace can only be used as the first
+operand of a member access or as the target of an `alias` declaration.
+
+```
+namespace MyNamespace;
+fn MyNamespace.MyFunction() {}
+
+// ✅ OK, can alias a namespace.
+alias MyNS = MyNamespace;
+fn CallMyFunction() { MyNS.MyFunction(); }
+
+// ❌ Error: a namespace is not a value.
+let MyNS2:! auto = MyNamespace;
+
+fn CallMyFunction2() {
+  // ❌ Error: cannot perform indirect member access into a namespace.
+  MyNamespace.(MyNamespace.MyFunction)();
+}
+```
+
+### 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 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 direct 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 an indirect 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:
+
+```
+interface Printable {
+  fn Print[me: Self]();
+}
+external impl i32 as Printable;
+class Point {
+  var x: i32;
+  var y: i32;
+  // Internal impl injects the name `Print` into class `Point`.
+  impl as Printable;
+}
+
+fn PrintPointTwice() {
+  var p: Point = {.x = 0, .y = 0};
+
+  // ✅ OK, `x` found in type of `p`, namely `Point`.
+  p.x = 1;
+  // ✅ OK, `y` found in the type `Point`.
+  p.(Point.y) = 1;
+
+  // ✅ OK, `Print` found in type of `p`, namely `Point`.
+  p.Print();
+  // ✅ OK, `Print` found in the type `Printable`.
+  p.(Printable.Print)();
+}
+fn GenericPrint[T:! Printable](a: T) {
+  // ✅ OK, type of `a` is the type parameter `T`;
+  // `Print` found in the type of `T`, namely `Printable`.
+  a.Print();
+}
+fn CallGenericPrint(p: Point) {
+  GenericPrint(p);
+}
+```
+
+#### Templates and generics
+
+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.
+
+```
+class GenericWrapper(T:! Type) {
+  var field: T;
+}
+fn F[T:! Type](x: GenericWrapper(T)) -> T {
+  // ✅ OK, finds `GenericWrapper(T).field`.
+  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[me: Self](); }
+interface Renderable {
+  fn Draw[me: Self]();
+}
+external impl Cowboy as Renderable { fn Draw[me: 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 RoundWidget {
+  external impl as Renderable {
+    fn Draw[me: Self]();
+  }
+  alias Draw = Renderable.Draw;
+}
+
+class SquareWidget {
+  fn Draw[me: Self]() {}
+  external impl as Renderable {
+    alias Draw = Self.Draw;
+  }
+}
+
+fn DrawWidget(r: RoundWidget, s: SquareWidget) {
+  // ✅ OK, lookup in type and lookup in type-of-type find the same entity.
+  DrawTemplate(r);
+
+  // ✅ OK, lookup in type and lookup in type-of-type find the same entity.
+  DrawTemplate(s);
+
+  // ✅ OK, found in type.
+  r.Draw();
+  s.Draw();
+}
+```
+
+#### 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.
+
+```carbon
+interface Addable {
+  // #1
+  fn Add[me: Self](other: Self) -> Self;
+  // #2
+  default fn Sum[Seq:! Iterable where .ValueType = Self](seq: Seq) -> Self {
+    // ...
+  }
+}
+
+class Integer {
+  impl as Addable {
+    // #3
+    fn Add[me: Self](other: Self) -> Self;
+    // #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);
+}
+```
+
+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[me: Self]() {}
+  let N:! i32;
+}
+class C {
+  impl as I where .N = 5 {
+    // #2
+    fn F[me: 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();
+```
+
+**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
+naming the interface member as a member of the class.
+
+```carbon
+interface Renderable {
+  // #1
+  fn Draw[me: Self]();
+}
+
+class RoundWidget {
+  external impl as Renderable {
+    // #2
+    fn Draw[me: Self]();
+  }
+  // `Draw` names the member of the `Renderable` interface.
+  alias Draw = Renderable.Draw;
+}
+
+class SquareWidget {
+  // #3
+  fn Draw[me: Self]() {}
+  external impl as Renderable {
+    alias Draw = Self.Draw;
+  }
+}
+
+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.
+  // The outer member access then forms a bound member function that
+  // calls #2 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.
+  // The outer member access then forms a bound member function that
+  // calls #3 on `s`.
+  s.(SquareWidget.Draw)();
+
+  // ❌ Error: In the inner member access, the name `Draw` resolves to the
+  // member `Draw` of `SquareWidget`, #3.
+  // The outer member access fails because we can't call
+  // #3, `Draw[me: 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.
+  // The outer member access fails because we can't call
+  // #2, `Draw[me: 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`
+      // to `Renderable.Draw`, which does not perform `impl` lookup yet.
+      // Then the indirect member access expression performs `impl` lookup
+      // into `impl T as Renderable`, since `T` is known to implement
+      // `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)();
+    }
+  }
+}
+
+class TriangleWidget extends WidgetBase {
+  external impl as Renderable;
+}
+fn DrawTriangle(t: TriangleWidget) {
+  // ✅ OK: name `Draw` resolves to `Draw` member of `WidgetBase`, which
+  // is `Renderable.Draw`. Then impl lookup replaces that with `Draw`
+  // member of `impl TriangleWidget as Renderable`.
+  t.Draw();
+}
+```
+
+## 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:
+
+-   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.
+
+    ```carbon
+    var dims: auto = {.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.height = 3;
+    ```
+
+-   For a method, the result is a _bound method_, which is a value `F` such that
+    a function call `F(args)` behaves the same as a call to `M(args)` with the
+    `me` parameter initialized by a corresponding recipient argument:
+
+    -   If the method declares its `me` parameter with `addr`, the recipient
+        argument is `&V`.
+    -   Otherwise, the recipient argument is `V`.
+
+    ```carbon
+    class Blob {
+      fn Mutate[addr me: Self*](n: i32);
+    }
+    fn F(p: Blob*) {
+      // ✅ OK, forms bound method `((*p).M)` and calls it.
+      // This calls `Blob.Mutate` with `me` initialized by `&(*p)`
+      // and `n` initialized by `5`.
+      (*p).Mutate(5);
+
+      // ✅ OK, same as above.
+      let bound_m: auto = (*p).Mutate;
+      bound_m(5);
+    }
+    ```
+
+## 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.
+
+An expression that names an instance member, but for which instance binding is
+not performed, can only be used as the second operand of an indirect member
+access or as the target of an `alias` declaration.
+
+```carbon
+class C {
+  fn StaticMethod();
+  var field: i32;
+  class Nested {}
+}
+fn CallStaticMethod(c: C) {
+  // ✅ OK, calls `C.StaticMethod`.
+  C.StaticMethod();
+
+  // ✅ OK, evaluates expression `c` then calls `C.StaticMethod`.
+  c.StaticMethod();
+
+  // ❌ 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;`
+  c.(C.field) = 1;
+
+  // ✅ OK
+  let T:! Type = C.Nested;
+  // ❌ Error: value of `:!` binding is not constant because it
+  // refers to local variable `c`.
+  let U:! Type = c.Nested;
+}
+```
+
+## Non-vacuous member access restriction
+
+The first operand of a member access expression must be used in some way: a
+member access must either be direct, so the first operand is used for lookup, or
+must result in `impl` lookup, instance binding, or both.
+
+```
+interface Printable {
+  fn Print[me: Self]();
+}
+external impl i32 as Printable {
+  fn Print[me: 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))();
+
+  // ✅ 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.
+  n.(Printable.Print)();
+}
+
+// ✅ OK, member `Print` of interface `Printable`.
+alias X1 = Printable.Print;
+// ❌ Error, indirect 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, indirect access doesn't perform impl lookup or instance binding.
+alias X4 = i32.(i32.(Printable.Print));
+```
+
+## Precedence and associativity
+
+Member access expressions associate left-to-right:
+
+```
+class A {
+  class B {
+    fn F();
+  }
+}
+interface B {
+  fn F();
+}
+external impl A as B;
+
+fn Use(a: A) {
+  // Calls member `F` of class `A.B`.
+  (a.B).F();
+  // Calls member `F` of interface `B`, as implemented by type `A`.
+  a.(B.F)();
+  // Same as `(a.B).F()`.
+  a.B.F();
+}
+```
+
+Member access has lower precedence than primary expressions, and higher
+precedence than all other expression forms.
+
+```
+// ✅ OK, `*` has lower precedence than `.`. Same as `(A.B)*`.
+var p: A.B*;
+// ✅ OK, `1 + (X.Y)` not `(1 + X).Y`.
+var n: i32 = 1 + X.Y;
+```
+
+## Alternatives considered
+
+-   [Separate syntax for static versus dynamic access, such as `::` versus `.`](/proposals/p0989.md#separate-syntax-for-static-versus-dynamic-access)
+-   [Use a different lookup rule for names in templates](/proposals/p0989.md#use-a-different-lookup-rule-in-templates)
+-   [Meaning of `Type.Interface](/proposals/p0989.md#meaning-of-typeinterface)
+
+## References
+
+-   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)

+ 346 - 0
proposals/p0989.md

@@ -0,0 +1,346 @@
+# Member access expressions
+
+<!--
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+Exceptions. See /LICENSE for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+-->
+
+[Pull request](https://github.com/carbon-language/carbon-lang/pull/989)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Details](#details)
+-   [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
+-   [Alternatives considered](#alternatives-considered)
+    -   [Separate syntax for static versus dynamic access](#separate-syntax-for-static-versus-dynamic-access)
+    -   [Use a different lookup rule in templates](#use-a-different-lookup-rule-in-templates)
+    -   [Meaning of `Type.Interface`](#meaning-of-typeinterface)
+
+<!-- tocstop -->
+
+## Problem
+
+We need syntaxes for a number of closely-related operations:
+
+-   Given an expression denoting a package, namespace, class, interface, or
+    similar, and the name of one of its members, form an expression denoting the
+    member. In C++ and Rust, this is spelled `Container::MemberName`. In many
+    other languages, it is spelled `Container.MemberName`.
+
+-   Given an expression denoting an object and a name of one of its fields, form
+    an expression denoting the corresponding subobject. This is commonly written
+    as `object.field`, with very little deviation across languages.
+
+-   Given an expression denoting an object and a name of one of its methods,
+    form an expression that calls the function on the object. This is commonly
+    written as `object.function(args)`.
+
+-   Given an expression denoting a type, and an expression denoting a member of
+    an interface, form an expression denoting the corresponding member in the
+    `impl` of that interface for that type.
+
+Further, we need rules describing how the lookup for the member name is
+performed, and how this lookup behaves in generics and in templates in cases
+where the member name depends on the type or value of the first operand.
+
+## Background
+
+C++ and Rust distinguish between the first use case and the rest. Other
+languages, such as Swift and C#, do not, and model all of these use cases as
+some generalized form of member access, where the member might be a namespace
+member, an interface member, an instance member, or similar.
+
+See also:
+
+-   [Exploration of member lookup in generic and non-generic contexts](https://docs.google.com/document/d/1-vw39x5YARpUZ0uD2xmKepLEKG7_u122CUJ67hNz3hk/edit)
+-   [Question for leads: constrained template name lookup](https://github.com/carbon-language/carbon-lang/issues/949)
+
+## Proposal
+
+All these operations are performed using `.`:
+
+```carbon
+fn F() {
+  // Can perform lookup inside the package or namespace.
+  var x: Package.Namespace.Class;
+  // Can perform lookup inside the type of the value.
+  x.some_field = x.SomeFunction(1, 2, 3);
+}
+```
+
+When the type of the left-hand operand is a generic type parameter, lookup is
+performed in its type-of-type instead. Effectively, a generic type parameter
+behaves as an archetype:
+
+```carbon
+interface Hashable {
+  let HashValue:! Type;
+  fn Hash[me: Self]() -> HashValue;
+  fn HashInto[me: Self](s: HashState);
+}
+fn G[T:! Hashable](x: T) {
+  // Can perform lookup inside the type-of-type if the type is
+  // a generic type parameter.
+  x.Hash();
+}
+```
+
+When the type of the left-hand operand is a template parameter, the lookup is
+performed both in the actual type corresponding to that template parameter and
+in the archetype, as described above. If a result is found in only one lookup,
+or the same result is found in both lookups, that result is used. Otherwise, the
+member access is invalid.
+
+```carbon
+class Potato {
+  fn Mash[me: Self]();
+  fn Hash[me: Self]();
+  alias HashValue = Hashable.HashValue;
+}
+external impl Potato as Hashable where .HashValue = u32 {
+  // ...
+}
+fn H[template T:! Hashable](x: T, s: HashState) {
+  // When called with T == Potato:
+  // ❌ Ambiguous, could be `Potato.Hash` or `Hashable.Hash`.
+  x.Hash();
+  // ✅ OK, found only in `Potato`.
+  x.Mash();
+  // ✅ OK, found only in `Hashable`.
+  x.HashInto(s);
+
+  // ✅ OK, same `HashValue` found in both `Potato` and `Hashable`;
+  // `Hashable.Hash` unambiguously names the interface member.
+  var v: T.HashValue = x.(Hashable.Hash)();
+
+  // ✅ OK, unambiguously names the type member.
+  x.(Potato.Hash)();
+}
+```
+
+## Details
+
+See
+[the changes to the design](https://github.com/carbon-language/carbon-lang/pull/989/files).
+
+## Rationale based on Carbon's goals
+
+-   [Software and language evolution](/docs/project/goals.md#software-and-language-evolution)
+    -   Rejecting cases in a template where a generic interpretation and an
+        interpretation with specific types would lead to different meanings
+        supports incremental migration towards generics by way of a template,
+        where the compiler will help you find places that would change meaning.
+-   [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
+    -   Using a single, familiar `container.member` notation for all the member
+        access use cases minimizes the complexity of this portion of the
+        language syntax.
+-   [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code)
+    -   The behavior of templates is aligned with that in C++, simplifying both
+        comprehension for C++ developers and migration of C++ code.
+
+## Alternatives considered
+
+### Separate syntax for static versus dynamic access
+
+We could follow C++ and Rust, and use `::` for static lookup, reserving `.` for
+instance binding:
+
+```
+var x: Package::Namespace::Class;
+Class::Function();
+x.field = x.Function();
+x.(Interface::Method)();
+```
+
+Advantages:
+
+-   Visually separates operations that readers may think of as being distinct: a
+    `::` path statically identifies an object whereas a `.` path dynamically
+    identifies a subobject or forms a bound method.
+-   Improves familiarity for those coming from C++.
+-   Removes most of the need for parenthesized member access: `a.(b.c)` would
+    generally become `a.b::c`, like in C++.
+
+Disadvantages:
+
+-   Adds a new token and a new operation.
+-   Swift, C#, and Java do not distinguish these operations syntactically, and
+    we have no evidence that this lack of syntactic distinction creates problems
+    for them in practice.
+-   Likely to result in complexity and inconsistency for operations falling
+    between the two options. For example, in C++:
+    ```
+    struct A {
+      static void F();
+      enum { e };
+    };
+    enum class B { e };
+    void G(A a, B b) {
+      a.F(); // OK, but static dispatch, like A::F().
+      a.e;   // OK, but static dispatch, like A::e.
+      b.e;   // Error.
+    }
+    ```
+-   Does not provide an obvious syntax for `impl` lookup.
+    `Type::Interface::method` would be ambiguous and `Type.Interface::method`
+    would be inconsistent with using `::` for static lookup, so we would likely
+    end up with `Type::(Interface::method)` syntax or similar.
+-   May create the suggestion that `.`s imply a performance-relevant operation
+    and `::`s do not. This will typically not be the case, as `.`s will
+    typically result in, at worst, a constant offset. However, `impl` lookup,
+    which may be performed by either a `.` or a `::`, may require a memory
+    access in cases where dynamic dispatch is in use.
+
+### Use a different lookup rule in templates
+
+See
+[question for leads: constrained template name lookup](https://github.com/carbon-language/carbon-lang/issues/949)
+for more in-depth discussion and leads decision.
+
+Given a situation where the same name can be found in both a type and a
+constraint when instantiating a template, and resolves to two different things,
+we could use various different rules to pick the outcome:
+
+```
+class Potato {
+  fn Bake[me: Self]();
+  fn Hash[me: Self]();
+}
+interface Hashable {
+  fn Hash[me: Self]() -> HashState;
+  fn HashInto[me: Self](s: HashState);
+}
+external impl Potato as Hashable;
+
+fn MakePotatoHash[template T:! Hashable](x: T, s: HashState) {
+  x.Bake();
+  x.Hash();
+  x.HashInto(s);
+}
+```
+
+We considered the following options:
+
+| Option                | Type only: `x.Bake()` | Both: `x.Hash()` | Constraint only: `x.HashInto(s)` |
+| --------------------- | --------------------- | ---------------- | -------------------------------- |
+| Type                  | -> Type               | -> Type          | ❌ Rejected                      |
+| Type over constraint  | -> Type               | -> Type          | -> Constraint                    |
+| Type minus conflicts  | -> Type               | -> Type          | ❌ Rejected                      |
+| Union minus conflicts | -> Type               | ❌ Rejected      | -> Constraint                    |
+| Constraint over type  | -> Type               | -> Constraint    | -> Constraint                    |
+| Constraint            | ❌ Rejected           | -> Constraint    | -> Constraint                    |
+
+Of these rules:
+
+-   "Type" and "type over constraint" mean the constraints in a constrained
+    template do not guide the meaning of the program, which creates a surprising
+    discontinuity when migrating from templates to generics.
+-   "Type minus conflicts" does not present a valuable improvement over "union
+    minus conflicts".
+-   "Union minus conflicts" makes the type-only case behave like a non-template,
+    and the constraint-only case behave like a generic. This means that explicit
+    qualification is necessary for all qualified names in a template if it wants
+    to defend against ambiguity from newly-added names, whereas all the earlier
+    options require qualification only for names intended to be found in the
+    constraint, and all the later options require qualification for names
+    intended to be found in the type. However, most of the other rules require
+    explicit qualification in the same cases to defend against names being
+    _removed_.
+-   "Constraint over type" means there is potential for a discontinuity in
+    behavior depending on whether we're able to symbolically resolve the type or
+    not: if semantic analysis can determine a type symbolically, you get the
+    behavior from the constraint, and if not, you get the behavior from the
+    type. This may lead to surprising and hard-to-understand program behavior.
+-   "Constraint" means that a constrained template behaves essentially the same
+    as a generic, which harms the ability to use constrained templates as an
+    incremental, evolutionary stepping stone from non-constrained templates into
+    generics.
+
+No rule provides ideal behavior. The most significant disadvantage of the chosen
+rule, "union minus conflicts", is that it requires explicit qualification with
+either the type or the constraint in a fully-robust template. However, the other
+leading contender, "constraint over type", also requires qualification of all
+names to prevent silent changes in behavior if a constraint is changed, and
+"union minus conflict" seems preferable to "constraint over type" in other ways.
+
+### Meaning of `Type.Interface`
+
+In this proposal, `impl` lookup is performed when a member of an interface
+appears on the right of a `.`. We could also consider applying `impl` lookup
+when the name of an interface appears on the right of a `.`. Under that
+alternative, `Class.(Interface)` would be a name for the `impl`, that is, for
+`impl Class as Interface`.
+
+Because we have previously decided we don't want facet types, such a name would
+be restricted to only appear in the same places where package and namespace
+names can appear: on the left of a `.` or the right of an `alias`.
+
+For example:
+
+```
+interface MyInterface {
+  fn F();
+  fn G[me: Self]();
+}
+class MyClass {
+  alias InterfaceAlias = MyInterface;
+  impl as MyInterface {
+    fn F();
+    fn G[me: Self]();
+  }
+}
+
+fn G(x: MyClass) {
+  // OK with this proposal and the alternative.
+  MyClass.(MyInterface.F)();
+  // Error with this proposal, OK with the alternative.
+  MyClass.(MyInterface).F();
+
+  // Names the interface with this proposal.
+  // Names the `impl` with the alternative.
+  alias AnotherInterfaceAlias = MyClass.InterfaceAlias;
+
+  // Error with this proposal, OK with the alternative.
+  MyClass.InterfaceAlias.F();
+  // OK with this proposal, error with the alternative.
+  MyClass.(MyClass.InterfaceAlias.F)();
+
+  // Error under this proposal, OK with the alternative.
+  x.MyInterface.F();
+  // Error under both this proposal.
+  // Also error under the alternative, unless we introduce
+  // a notion of a "bound `impl`" so that `x.MyInterface`
+  // remembers its receiver object.
+  x.MyInterface.G();
+  // OK under this proposal and the alternative.
+  x.(MyInterface.G)();
+}
+```
+
+Advantages:
+
+-   Gives a way to name an `impl`.
+
+Disadvantages:
+
+-   It's not clear that we need a way to name an `impl`.
+-   Presents a barrier to supporting member interfaces, because
+    `MyClass.MemberInterface` would name the `impl MemberInterface as MyClass`,
+    not the interface itself.
+-   Reintroduces facet types, without the ability to use them as a type. Having
+    a way of naming an `impl` may lead to confusion over whether they are
+    first-class entities.
+-   Would either surprisingly reject constructs like `x.MyInterface.G()` or
+    require additional complexity in the form of a "bound `impl`" value. The
+    value of such a type would presumably be equivalent to a facet type.
+
+As a variant of this alternative, we could disallow `Type.Interface` for now, in
+order to reserve syntactic space for a future decision. However, it's not clear
+that the cost of evolution nor the likelihood of such a change is sufficiently
+high to warrant including such a rule.