From df435878d8aba755d6ebbcf093596f29ea51e107 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 14 Dec 2021 19:06:42 -0800 Subject: [PATCH 01/36] Initial design changes for member access expressions --- docs/design/expressions/README.md | 57 +++++ docs/design/expressions/member_access.md | 293 +++++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 docs/design/expressions/member_access.md diff --git a/docs/design/expressions/README.md b/docs/design/expressions/README.md index 9654c3e1a5c4b..8ee2c0b0bd9b6 100644 --- a/docs/design/expressions/README.md +++ b/docs/design/expressions/README.md @@ -11,6 +11,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents - [Overview](#overview) +- [Names](#names) + - [Unqualified names](#unqualified-names) + - [Qualified names and member access](#qualified-names-and-member-access) - [Conversions and casts](#conversions-and-casts) @@ -29,6 +32,60 @@ fn Foo(a: i32*) -> i32 { Here, the parameter type `i32*`, the return type `i32`, and the operand `*a` of the `return` statement are all expressions. +## 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 as designators and in [member access](member_access.md) expressions of +the form + +> _expression_ `.` _word_ + +or + +> _expression_ `.` `(` _member-access-expression_ `)` + +Qualified names refer to members of the entity named by the expression preceding +the period. For example: + +``` +package Foo api; +namespace N; +fn N.F() {} + +fn G() { + // `Foo.N` names namespace `N` in package `Foo`. + // `(Foo.N).F` names function `F` in namespace `N`. + Foo.N.F(); +} + +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; +} +``` + +Member access expressions associate left-to-right. If the member name is more +complex than a single _word_, parentheses are required: + +``` +interface I { fn F[me: Self](); } +class X {} +impl X as I { fn F[me: Self]() {} } + +// x.I.F() would mean (x.I).F(). +fn Q(x: X) { x.(I.F)(); } +``` + ## Conversions and casts When an expression appears in a context in which an expression of a specific diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md new file mode 100644 index 0000000000000..443caee10f70a --- /dev/null +++ b/docs/design/expressions/member_access.md @@ -0,0 +1,293 @@ +# Qualified names and member access + + + + + +## Table of contents + +- [Overview](#overview) +- [Package and namespace members](#package-and-namespace-members) +- [Lookup within values](#lookup-within-values) + - [Templates and generics](#templates-and-generics) +- [Member access](#member-access) +- [Precedence and associativity](#precedence-and-associativity) +- [Alternatives considered](#alternatives-considered) +- [References](#references) + + + +## 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 found or declared within + the struct. + +A _member access expression_ allows a member of a value, type, interface, +namespace, etc. 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 qualified name_ of the form: + +> _member-access-expression_ ::= _expression_ `.` `(` _member-access-expression_ `)` + +The meaning of a qualified name in a member access expression depends on the +first operand, which can be: + +- A [package or namespace name](#package-and-namespace-members). +- A [value of some type](#lookup-within-values). + +## 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 entity 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 N; +fn N.F() {} + +// OK, can alias a namespace. +alias M = N; +fn G() { M.F(); } + +// Error: a namespace is not a value. +let M2:! auto = N; + +fn H() { + // Error: cannot perform indirect member access into a namespace. + N.(N.F()); +} +``` + +## 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 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`. +- 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 value, and lookup should consider members of the + value's type; if that type is a type parameter, lookup should consider + members of the type-of-type, treating the type parameter as an archetype. + +Note that because a type is a value, and a type-of-type is a type, these cases +are overlapping and not entirely separable. + +For a direct member access, the word is [looked up](#type-members) 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. If more than one distinct entity is +found, the qualified name is invalid. + +For an indirect member access, the second operand is evaluated to determine the +member being accessed. + +For example: + +``` +class A { + var x: i32; +} +interface I { + fn F[me: Self](); +} +impl A as I { + fn F[me: Self]() {} +} +fn Test(a: A) { + // OK, `x` found in type of `a`, namely `A`. + a.x = 1; + // OK, `x` found in the type `A`. + a.(A.x) = 1; + + // OK, `F` found in type of `a`, namely `A`. + a.F(); + // OK, `F` found in the type `I`. + a.(I.F)(); +} +fn GenericTest[T: I](a: T) { + // OK, type of `a` is the type parameter `T`; + // `F` found in the type of `T`, namely `I`. + a.F(); +} +fn CallGenericTest(a: A) { + GenericTest(a); +} +``` + +The resulting member is then [accessed](#member-access) within the value denoted +by the first operand. + +### 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. + +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, and if more than one distinct entity is found, +the qualified name is invalid. + +The lookup for a member name never considers the values of any generic +parameters that are in scope at the point where the member name appears. + +``` +class A { fn F[me: Self]() {} } +interface I { fn F[me: Self](); } +impl A as I { fn F[me: Self]() {} } +fn UseDirect(a: A) { a.F(); } +fn UseGeneric[T:! I](a: T) { a.F(); } +fn UseTemplate[template T:! I](a: T) { a.F(); } + +fn Use(a: A) { + // Calls member of `A`. + UseDirect(a); + // Calls member of `impl A as I`. + UseGeneric(a); + // Error: ambiguous. + UseTemplate(a); +} + +class B { + fn F[me: Self]() {} + impl as I { + alias F = Self.F; + } +} +class C { + fn F[me: Self]() {} +} +impl C as I { + alias F = C.F; +} + +fn UseBC(b: B, c: C) { + // OK, lookup in type and in type-of-type find the same entity. + UseTemplate(b); + + // OK, lookup in type and in type-of-type find the same entity. + UseTemplate(c); +} +``` + +## Member access + +A member `M` is accessed within a value `V` as follows: + +- If the member is an instance member -- a field or a method -- and was either + named indirectly or was named directly within the type or type-of-type of + the value, the result is: + + - For a field member, the corresponding subobject within the value. + - For a method, 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 `V`. + +- Otherwise, the member access must be direct, and the result is the member, + but evaluating the member access expression still evaluates `V`. An + expression that names an instance member can only be used as the second + operand of a member access or as the target of an `alias` declaration. + +``` +class A { + fn F[me: Self](); + fn G(); + var v: i32; + class B {}; +} +fn H(a: A) { + // OK, calls `A.F` with `me` initialized by `a`. + a.F(); + + // OK, same as above. + var bound_f: auto = a.F; + bound_f(); + + // OK, calls `A.G`. + A.G(); + // OK, evaluates expression `a` then calls `A.G`. + a.G(); + + // Error: name of instance member `A.v` can only be used in a + // member access or alias. + A.v = 1; + // OK + a.v = 1; + + // OK + let T:! Type = A.B; + // Error: value of `:!` binding is not constant because it + // refers to local variable `a`. + let U:! Type = a.B; +} +``` + +## Precedence and associativity + +Member access expressions associate left-to-right: + +``` +class A { + class B { + fn F(); + } +} +interface B { + fn F(); +} +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 `.`. +var p: A.B*; +// OK, `1 + (X.Y)` not `(1 + X).Y`. +var n: i32 = 1 + X.Y; +``` + +## Alternatives considered + +- [...](/proposals/p0123.md#foo) + +## References + +- Proposal + [#123: title](https://github.com/carbon-language/carbon-lang/pull/123) From b72d322cb64aa6831d6c22adc164415ea85fbbd7 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 14 Dec 2021 19:10:07 -0800 Subject: [PATCH 02/36] Filling out template with PR 989 --- proposals/p0989.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 proposals/p0989.md diff --git a/proposals/p0989.md b/proposals/p0989.md new file mode 100644 index 0000000000000..a55e3d98a8c04 --- /dev/null +++ b/proposals/p0989.md @@ -0,0 +1,62 @@ +# Member access expressions + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/989) + + + +## 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) + + + +## Problem + +TODO: What problem are you trying to solve? How important is that problem? Who +is impacted by it? + +## Background + +TODO: Is there any background that readers should consider to fully understand +this problem and your approach to solving it? + +## Proposal + +TODO: Briefly and at a high level, how do you propose to solve the problem? Why +will that in fact solve it? + +## Details + +TODO: Fully explain the details of the proposed solution. + +## Rationale based on Carbon's goals + +TODO: How does this proposal effectively advance Carbon's goals? Rather than +re-stating the full motivation, this should connect that motivation back to +Carbon's stated goals for the project or language. This may evolve during +review. Use links to appropriate goals, for example: + +- [Community and culture](/docs/project/goals.md#community-and-culture) +- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) +- [Performance-critical software](/docs/project/goals.md#performance-critical-software) +- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) +- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) +- [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms) +- [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development) +- [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments) +- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code) + +## Alternatives considered + +TODO: What alternative solutions have you considered? From efe4d7333d5529b28d5ea8e261a6dd1061f64e1c Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 15 Dec 2021 17:19:42 -0800 Subject: [PATCH 03/36] Rules for member access naming interface members. --- docs/design/expressions/member_access.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 443caee10f70a..3603fbcc3b33d 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -200,6 +200,11 @@ fn UseBC(b: B, c: C) { A member `M` is accessed within a value `V` as follows: +- If `M` is a member of interface `I`, then the member of the corresponding + `impl T as I` is looked up and used in the place of `M`, where `T` is `V` if + `V` can be evaluated and evaluates to a type, and `T` is the type of `V` + otherwise. + - If the member is an instance member -- a field or a method -- and was either named indirectly or was named directly within the type or type-of-type of the value, the result is: @@ -246,6 +251,23 @@ fn H(a: A) { // refers to local variable `a`. let U:! Type = a.B; } + +interface I { + fn J[me: Self](); +} +impl A as I { + fn J[me: Self]() {} +} +fn K(a: A) { + // OK: `I.J` is the interface member. + // `A.(I.J)` is the corresponding member of the `impl`. + // `a.(A.(I.J))` is a bound member function naming that member. + a.(A.(I.J))(); + + // Same as above, `a.(I.J)` is interpreted as `a.(A.(I.J))()` + // because `a` does not evaluate to a type. + a.(I.J)(); +} ``` ## Precedence and associativity From 9e7e021fc202cfbf43b8e689376e4bfd55ef3e41 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 18 Jan 2022 17:20:06 -0800 Subject: [PATCH 04/36] Text improvements --- docs/design/expressions/README.md | 14 +++- docs/design/expressions/member_access.md | 83 +++++++++++++++--------- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/docs/design/expressions/README.md b/docs/design/expressions/README.md index 8ee2c0b0bd9b6..076b905adc6b6 100644 --- a/docs/design/expressions/README.md +++ b/docs/design/expressions/README.md @@ -53,8 +53,10 @@ or > _expression_ `.` `(` _member-access-expression_ `)` -Qualified names refer to members of the entity named by the expression preceding -the period. For example: +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; @@ -67,11 +69,17 @@ fn G() { 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 @@ -82,7 +90,7 @@ interface I { fn F[me: Self](); } class X {} impl X as I { fn F[me: Self]() {} } -// x.I.F() would mean (x.I).F(). +// `x.I.F()` would mean `(x.I).F()`. fn Q(x: X) { x.(I.F)(); } ``` diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 3603fbcc3b33d..de7fc30b808fe 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -27,8 +27,8 @@ 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 found or declared within - the struct. +- 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, etc. to be accessed by specifying a qualified name for the member. A @@ -37,9 +37,10 @@ form: > _member-access-expression_ ::= _expression_ `.` _word_ -or an _indirect qualified name_ of the form: +or an _indirect_ member access of the form: -> _member-access-expression_ ::= _expression_ `.` `(` _member-access-expression_ `)` +> _member-access-expression_ ::= _expression_ `.` `(` +> _member-access-expression_ > `)` The meaning of a qualified name in a member access expression depends on the first operand, which can be: @@ -51,7 +52,7 @@ first operand, which can be: 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 entity with that name. +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. @@ -78,24 +79,24 @@ fn H() { 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 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`. -- 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 value, and lookup should consider members of the value's type; if that type is a type parameter, lookup should consider members of the type-of-type, treating the type parameter as an archetype. +- 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`. Note that because a type is a value, and a type-of-type is a type, these cases are overlapping and not entirely separable. -For a direct member access, the word is [looked up](#type-members) in the -following types: +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 can be evaluated, that type. Results found + by this lookup are said to be [_immediate_ results](#member-access). - 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. @@ -175,16 +176,16 @@ fn Use(a: A) { } class B { - fn F[me: Self]() {} impl as I { - alias F = Self.F; + fn F[me: Self]() {} } + alias F = I.F; } class C { fn F[me: Self]() {} -} -impl C as I { - alias F = C.F; + impl as I { + alias F = Self.F; + } } fn UseBC(b: B, c: C) { @@ -193,6 +194,13 @@ fn UseBC(b: B, c: C) { // OK, lookup in type and in type-of-type find the same entity. UseTemplate(c); + + // Error, can't call `F[me: C]()` on `B` object. + b.(C.F)(); + + // Error, member access resolves `B.F` to the member `F` of `impl B as I`; + // can't call `F[me: B]()` on `C` object. + c.(B.F)(); } ``` @@ -200,24 +208,29 @@ fn UseBC(b: B, c: C) { A member `M` is accessed within a value `V` as follows: -- If `M` is a member of interface `I`, then the member of the corresponding +- _`impl` lookup:_ If `M` is a member of interface `I` and `V` does not + evaluate to a type-of-type, then the member of the corresponding `impl T as I` is looked up and used in the place of `M`, where `T` is `V` if `V` can be evaluated and evaluates to a type, and `T` is the type of `V` - otherwise. + otherwise. The resulting `impl` member is not an immediate result. -- If the member is an instance member -- a field or a method -- and was either - named indirectly or was named directly within the type or type-of-type of - the value, the result is: +- `_Instance binding`: If the member is an instance member -- a field or a + method -- and is not an immediate result (as described above), `V` is + implicitly converted to the `me` type of the member, and the result is: - - For a field member, the corresponding subobject within the value. + - For a field member, the corresponding subobject within the converted + `V`. - For a method, 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 `V`. -- Otherwise, the member access must be direct, and the result is the member, - but evaluating the member access expression still evaluates `V`. An - expression that names an instance member can only be used as the second - operand of a member access or as the target of an `alias` declaration. +- If instance binding is not performed, the result is the member, but + evaluating the member access expression still evaluates `V`. An expression + that names an instance member can only be used as the second operand of a + member access or as the target of an `alias` declaration. + +The first operand must be used in some way: an indirect access must result in +either `impl` lookup, instance binding, or both. ``` class A { @@ -265,9 +278,19 @@ fn K(a: A) { a.(A.(I.J))(); // Same as above, `a.(I.J)` is interpreted as `a.(A.(I.J))()` - // because `a` does not evaluate to a type. + // because `a` does not evaluate to a type. Performs impl lookup + // and then instance binding. a.(I.J)(); } + +// OK, member `J` of interface I. +alias X1 = I.J; +// Error, indirect access doesn't perform impl lookup or instance binding. +alias X2 = I.(I.J); +// OK, member `J` of `impl A as I`. +alias X3 = A.(I.J); +// Error, indirect access doesn't perform impl lookup or instance binding. +alias X4 = A.(A.(I.J)); ``` ## Precedence and associativity From 4ea4f73b30de24c19922b969f11dc8a9b0666ce6 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 19 Jan 2022 15:50:39 -0800 Subject: [PATCH 05/36] Add proposal text. --- docs/design/expressions/member_access.md | 29 +++++- proposals/p0989.md | 117 +++++++++++++++++++---- 2 files changed, 124 insertions(+), 22 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index de7fc30b808fe..ba00dc1ddecf8 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -87,7 +87,8 @@ remaining cases we wish to support: 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`. + 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. @@ -149,6 +150,28 @@ 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 Generic(T:! Type) { + var field: T; +} +fn F[T:! Type](x: Generic(T)) -> T { + // OK, finds `Generic(T).field`. + return x.field; +} + +class Template(template T:! Type) { + var field: T; +} +fn G[template T:! Type](x: Template(T)) -> T { + 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 `Template(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 @@ -330,9 +353,9 @@ var n: i32 = 1 + X.Y; ## Alternatives considered -- [...](/proposals/p0123.md#foo) +- [Constrained template name lookup alternatives](https://github.com/carbon-language/carbon-lang/issues/949) ## References - Proposal - [#123: title](https://github.com/carbon-language/carbon-lang/pull/123) + [#989: member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) diff --git a/proposals/p0989.md b/proposals/p0989.md index a55e3d98a8c04..fcf9501ae4242 100644 --- a/proposals/p0989.md +++ b/proposals/p0989.md @@ -23,40 +23,119 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Problem -TODO: What problem are you trying to solve? How important is that problem? Who -is impacted by it? +We need syntaxes for a number of closely-related operations: + +- Given an expression denoting a package, namespace, class, interface, etc. + and the name of one of its members, form an expression denoting the member. + In C++, 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 -TODO: Is there any background that readers should consider to fully understand -this problem and your approach to solving it? +C++ distinguishes between the first use case and the rest. Other languages, such +as Rust, Swift, C#, and so on, 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. ## Proposal -TODO: Briefly and at a high level, how do you propose to solve the problem? Why -will that in fact solve it? +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; +} +extern 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 -TODO: Fully explain the details of the proposed solution. +See +[the changes to the design](https://github.com/carbon-language/carbon-lang/pull/989/files). ## Rationale based on Carbon's goals -TODO: How does this proposal effectively advance Carbon's goals? Rather than -re-stating the full motivation, this should connect that motivation back to -Carbon's stated goals for the project or language. This may evolve during -review. Use links to appropriate goals, for example: - -- [Community and culture](/docs/project/goals.md#community-and-culture) -- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) -- [Performance-critical software](/docs/project/goals.md#performance-critical-software) - [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) + - 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) -- [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms) -- [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development) -- [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments) + - 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 -TODO: What alternative solutions have you considered? +- [Question for leads: constrained template name lookup](https://github.com/carbon-language/carbon-lang/issues/949) From 46dde7fbf04ee99d8b9044cffc989f3f136fc432 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 19 Jan 2022 15:55:04 -0800 Subject: [PATCH 06/36] Undo some damage done by `prettier`. --- docs/design/expressions/member_access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index ba00dc1ddecf8..30f9d5ba5afd7 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -40,7 +40,7 @@ form: or an _indirect_ member access of the form: > _member-access-expression_ ::= _expression_ `.` `(` -> _member-access-expression_ > `)` +> _member-access-expression_ `)` The meaning of a qualified name in a member access expression depends on the first operand, which can be: From 138470d374d24c2d9f7beeee4087585f836218cb Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 19 Jan 2022 16:10:42 -0800 Subject: [PATCH 07/36] Switch from blockquote to bullets to work around prettier bug. --- docs/design/expressions/member_access.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 30f9d5ba5afd7..02754d36f0d78 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -35,12 +35,12 @@ namespace, etc. 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_ +- _member-access-expression_ ::= _expression_ `.` _word_ or an _indirect_ member access of the form: -> _member-access-expression_ ::= _expression_ `.` `(` -> _member-access-expression_ `)` +- _member-access-expression_ ::= _expression_ `.` `(` + _member-access-expression_ `)` The meaning of a qualified name in a member access expression depends on the first operand, which can be: From 5413d9bef5e90a3a7509ea3ca227a089e3549fcf Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 21 Jan 2022 15:48:09 -0800 Subject: [PATCH 08/36] Add some cross-references to proposal background. --- proposals/p0989.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proposals/p0989.md b/proposals/p0989.md index fcf9501ae4242..f868373ef631b 100644 --- a/proposals/p0989.md +++ b/proposals/p0989.md @@ -53,6 +53,11 @@ as Rust, Swift, C#, and so on, 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 `.`: From 93d95638b67406c1af4cc3ac98685dfe1af5d2d4 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 21 Jan 2022 17:10:06 -0800 Subject: [PATCH 09/36] Address review comments. --- docs/design/expressions/README.md | 22 +++- docs/design/expressions/member_access.md | 146 +++++++++++++---------- 2 files changed, 97 insertions(+), 71 deletions(-) diff --git a/docs/design/expressions/README.md b/docs/design/expressions/README.md index 076b905adc6b6..38664261ca443 100644 --- a/docs/design/expressions/README.md +++ b/docs/design/expressions/README.md @@ -44,14 +44,21 @@ keyword and is not preceded by a period (`.`). ### Qualified names and member access A _qualified name_ is a word that is prefixed by a period. Qualified names -appear as designators and in [member access](member_access.md) expressions of -the form +appear in the following contexts: -> _expression_ `.` _word_ +- [Designators](/docs/design/classes.md#literals): `.` _word_ +- [Direct member access expressions](member_access.md): _expression_ `.` + _word_ -or +``` +var x: auto = {.hello = 1, .world = 2}; + ^^^^^ ^^^^^ qualified name + ^^^^^^ ^^^^^^ designator -> _expression_ `.` `(` _member-access-expression_ `)` +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 @@ -83,7 +90,10 @@ fn J() { ``` Member access expressions associate left-to-right. If the member name is more -complex than a single _word_, parentheses are required: +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](); } diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 02754d36f0d78..047b091832dba 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -61,15 +61,15 @@ operand of a member access or as the target of an `alias` declaration. namespace N; fn N.F() {} -// OK, can alias a namespace. +// ✅ OK, can alias a namespace. alias M = N; fn G() { M.F(); } -// Error: a namespace is not a value. +// ❌ Error: a namespace is not a value. let M2:! auto = N; fn H() { - // Error: cannot perform indirect member access into a namespace. + // ❌ Error: cannot perform indirect member access into a namespace. N.(N.F()); } ``` @@ -120,18 +120,18 @@ impl A as I { fn F[me: Self]() {} } fn Test(a: A) { - // OK, `x` found in type of `a`, namely `A`. + // ✅ OK, `x` found in type of `a`, namely `A`. a.x = 1; - // OK, `x` found in the type `A`. + // ✅ OK, `x` found in the type `A`. a.(A.x) = 1; - // OK, `F` found in type of `a`, namely `A`. + // ✅ OK, `F` found in type of `a`, namely `A`. a.F(); - // OK, `F` found in the type `I`. + // ✅ OK, `F` found in the type `I`. a.(I.F)(); } fn GenericTest[T: I](a: T) { - // OK, type of `a` is the type parameter `T`; + // ✅ OK, type of `a` is the type parameter `T`; // `F` found in the type of `T`, namely `I`. a.F(); } @@ -151,26 +151,27 @@ parameter is unknown. Evaluation of an expression involving the parameter may still succeed, but will result in a symbolic value involving that parameter. ``` -class Generic(T:! Type) { +class GenericWrapper(T:! Type) { var field: T; } -fn F[T:! Type](x: Generic(T)) -> T { - // OK, finds `Generic(T).field`. +fn F[T:! Type](x: GenericWrapper(T)) -> T { + // ✅ OK, finds `GenericWrapper(T).field`. return x.field; } -class Template(template T:! Type) { +class TemplateWrapper(template T:! Type) { var field: T; } -fn G[template T:! Type](x: Template(T)) -> T { +fn G[template T:! Type](x: TemplateWrapper(T)) -> T { 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 `Template(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. +> 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 @@ -181,49 +182,64 @@ the qualified name is invalid. The lookup for a member name never considers the values of any generic parameters that are in scope at the point where the member name appears. -``` -class A { fn F[me: Self]() {} } -interface I { fn F[me: Self](); } -impl A as I { fn F[me: Self]() {} } -fn UseDirect(a: A) { a.F(); } -fn UseGeneric[T:! I](a: T) { a.F(); } -fn UseTemplate[template T:! I](a: T) { a.F(); } - -fn Use(a: A) { - // Calls member of `A`. - UseDirect(a); - // Calls member of `impl A as I`. - UseGeneric(a); - // Error: ambiguous. - UseTemplate(a); +```carbon +class Cowboy { fn Draw[me: Self](); } +interface Renderable { fn Draw[me: Self](); } +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 B { - impl as I { - fn F[me: Self]() {} +class RoundWidget { + impl as Renderable { + fn Draw[me: Self](); } - alias F = I.F; + alias Draw = Renderable.Draw; } -class C { - fn F[me: Self]() {} - impl as I { - alias F = Self.F; + +class SquareWidget { + fn Draw[me: Self]() {} + impl as Renderable { + alias Draw = Self.Draw; } } -fn UseBC(b: B, c: C) { - // OK, lookup in type and in type-of-type find the same entity. - UseTemplate(b); +fn DrawWidget(r: RoundWidget, s: SquareWidget) { + // ✅ OK, lookup in type and in type-of-type find the same entity. + UseTemplate(r); + + // ✅ OK, lookup in type and in type-of-type find the same entity. + UseTemplate(s); + + // ✅ OK, found in type. + r.Draw(); + s.Draw(); + + // ✅ OK, inner member access resolves `RoundWidget.Draw` to + // the member `Draw` of `impl RoundWidget as Renderable`, + // outer member access forms a bound member function. + r.(RoundWidget.Draw)(); - // OK, lookup in type and in type-of-type find the same entity. - UseTemplate(c); + // ✅ OK, inner member access names `SquareWidget.Draw`, + // outer member access forms a bound member function. + s.(SquareWidget.Draw)(); - // Error, can't call `F[me: C]()` on `B` object. - b.(C.F)(); + // ❌ Error, can't call `Draw[me: SquareWidget]()` on `RoundWidget` object. + r.(SquareWidget.Draw)(); - // Error, member access resolves `B.F` to the member `F` of `impl B as I`; - // can't call `F[me: B]()` on `C` object. - c.(B.F)(); + // ❌ Error, inner member access resolves `RoundWidget.Draw` to + // the member `Draw` of `impl RoundWidget as Renderable`; + // can't call `Draw[me: RoundWidget]()` on `SquareWidget` object. + s.(RoundWidget.Draw)(); } ``` @@ -263,27 +279,27 @@ class A { class B {}; } fn H(a: A) { - // OK, calls `A.F` with `me` initialized by `a`. + // ✅ OK, calls `A.F` with `me` initialized by `a`. a.F(); - // OK, same as above. + // ✅ OK, same as above. var bound_f: auto = a.F; bound_f(); - // OK, calls `A.G`. + // ✅ OK, calls `A.G`. A.G(); - // OK, evaluates expression `a` then calls `A.G`. + // ✅ OK, evaluates expression `a` then calls `A.G`. a.G(); - // Error: name of instance member `A.v` can only be used in a + // ❌ Error: name of instance member `A.v` can only be used in a // member access or alias. A.v = 1; - // OK + // ✅ OK a.v = 1; - // OK + // ✅ OK let T:! Type = A.B; - // Error: value of `:!` binding is not constant because it + // ❌ Error: value of `:!` binding is not constant because it // refers to local variable `a`. let U:! Type = a.B; } @@ -295,24 +311,24 @@ impl A as I { fn J[me: Self]() {} } fn K(a: A) { - // OK: `I.J` is the interface member. + // ✅ OK: `I.J` is the interface member. // `A.(I.J)` is the corresponding member of the `impl`. // `a.(A.(I.J))` is a bound member function naming that member. a.(A.(I.J))(); - // Same as above, `a.(I.J)` is interpreted as `a.(A.(I.J))()` + // ✅ Same as above, `a.(I.J)` is interpreted as `a.(A.(I.J))()` // because `a` does not evaluate to a type. Performs impl lookup // and then instance binding. a.(I.J)(); } -// OK, member `J` of interface I. +// ✅ OK, member `J` of interface I. alias X1 = I.J; -// Error, indirect access doesn't perform impl lookup or instance binding. +// ❌ Error, indirect access doesn't perform impl lookup or instance binding. alias X2 = I.(I.J); -// OK, member `J` of `impl A as I`. +// ✅ OK, member `J` of `impl A as I`. alias X3 = A.(I.J); -// Error, indirect access doesn't perform impl lookup or instance binding. +// ❌ Error, indirect access doesn't perform impl lookup or instance binding. alias X4 = A.(A.(I.J)); ``` @@ -345,9 +361,9 @@ Member access has lower precedence than primary expressions, and higher precedence than all other expression forms. ``` -// OK, `*` has lower precedence than `.`. +// ✅ OK, `*` has lower precedence than `.`. var p: A.B*; -// OK, `1 + (X.Y)` not `(1 + X).Y`. +// ✅ OK, `1 + (X.Y)` not `(1 + X).Y`. var n: i32 = 1 + X.Y; ``` From 9ac1f4dd884d9f4568ed0bd6041ba8d7abefd6cb Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 21 Jan 2022 17:17:18 -0800 Subject: [PATCH 10/36] Apply suggestions from code review Co-authored-by: josh11b --- docs/design/expressions/README.md | 2 +- docs/design/expressions/member_access.md | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/design/expressions/README.md b/docs/design/expressions/README.md index 38664261ca443..5655468bee1d6 100644 --- a/docs/design/expressions/README.md +++ b/docs/design/expressions/README.md @@ -98,7 +98,7 @@ with parentheses around the member name: ``` interface I { fn F[me: Self](); } class X {} -impl X as I { fn F[me: Self]() {} } +external impl X as I { fn F[me: Self]() {} } // `x.I.F()` would mean `(x.I).F()`. fn Q(x: X) { x.(I.F)(); } diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 047b091832dba..bdd0784cd66ee 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -31,7 +31,7 @@ by a period. The name is found within a contextually-determined entity: the struct type. A _member access expression_ allows a member of a value, type, interface, -namespace, etc. to be accessed by specifying a qualified name for the member. A +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: @@ -70,7 +70,7 @@ let M2:! auto = N; fn H() { // ❌ Error: cannot perform indirect member access into a namespace. - N.(N.F()); + N.(N.F)(); } ``` @@ -110,14 +110,14 @@ member being accessed. For example: ``` -class A { - var x: i32; -} interface I { fn F[me: Self](); } -impl A as I { - fn F[me: Self]() {} +class A { + var x: i32; + impl as I { + fn F[me: Self]() {} + } } fn Test(a: A) { // ✅ OK, `x` found in type of `a`, namely `A`. From 5cf7eb1f77480bb02ca15decd39df594fcbebd70 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 21 Jan 2022 18:31:18 -0800 Subject: [PATCH 11/36] Address review comments. --- docs/design/expressions/README.md | 1 + docs/design/expressions/member_access.md | 210 +++++++++++++++-------- proposals/p0989.md | 16 +- 3 files changed, 146 insertions(+), 81 deletions(-) diff --git a/docs/design/expressions/README.md b/docs/design/expressions/README.md index 5655468bee1d6..c45180a51ab78 100644 --- a/docs/design/expressions/README.md +++ b/docs/design/expressions/README.md @@ -71,6 +71,7 @@ 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(); diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index bdd0784cd66ee..895ba315697bf 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -31,9 +31,9 @@ by a period. The name is found within a contextually-determined entity: 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: +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_ @@ -80,8 +80,7 @@ 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; if that type is a type parameter, lookup should consider - members of the type-of-type, treating the type parameter as an archetype. + 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`. @@ -93,11 +92,14 @@ remaining cases we wish to support: 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. + 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. Results found - by this lookup are said to be [_immediate_ results](#member-access). +- 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. @@ -247,89 +249,151 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { A member `M` is accessed within a value `V` as follows: -- _`impl` lookup:_ If `M` is a member of interface `I` and `V` does not - evaluate to a type-of-type, then the member of the corresponding - `impl T as I` is looked up and used in the place of `M`, where `T` is `V` if - `V` can be evaluated and evaluates to a type, and `T` is the type of `V` - otherwise. The resulting `impl` member is not an immediate result. - -- `_Instance binding`: If the member is an instance member -- a field or a - method -- and is not an immediate result (as described above), `V` is - implicitly converted to the `me` type of the member, and the result is: - - - For a field member, the corresponding subobject within the converted - `V`. - - For a method, a _bound method_, which is a value `F` such that a +- _`impl` lookup:_ If `M` is a member of an interface `I` and `V` does not + evaluate to a type-of-type, then `M` is replaced by the corresponding member + of an implementation of `I`. + + The type `T` that is expected to implement `I` is `V` if `V` can be + evaluated and evaluates to a type, and `T` is the type of `V` otherwise. The + appropriate `impl T as I` implementation is located. `M` is replaced by the + member of that `impl` that corresponds to `M`. + + ```carbon + interface I { + // #1 + fn F[me: Self]() {} + } + class C { + impl as I { + // #2 + fn F[me: C]() {} + } + } + + // `M` is `I.F` and `V` is `I`. Because `V` is a type-of-type, + // `impl` lookup is not performed, and the alias binds to #1. + alias A1 = I.F; + + // `M` is `I.F` and `V` is `C`. Because `V` is a type, `T` is `C`. + // `impl` lookup is performed, and the alias binds to #2. + alias A2 = C.F; + ``` + + Instance binding may also apply if the member is an instance member. + + ```carbon + var c: C; + // `M` is `I.F` and `V` is `c`. 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(); + ``` + +- _Instance binding:_ If the member is an instance member -- a field or a + method -- and `V` does not evaluate to a type, then: + + - 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, `V` is converted to the recipient type, as follows. First, + if the method declares its recipient type with `addr`, then `V` is + replaced by `&V`. Then, `V` is implicitly converted to the declared `me` + type. + + 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 `V`. + ```carbon + class C { + fn M[addr me: Self*](n: i32); + } + fn F(c: C) { + // ✅ OK, forms bound method `(c.M)` and calls it. + // This calls `C.M` with `me` initialized by `&c` + // and `n` initialized by `5`. + c.M(5); + + // ✅ OK, same as above. + var bound_m: auto = c.M; + bound_m(5); + } + ``` + - If instance binding is not performed, the result is the member, but evaluating the member access expression still evaluates `V`. An expression that names an instance member can only be used as the second operand of a member access or as the target of an `alias` declaration. -The first operand must be used in some way: an indirect access must result in -either `impl` lookup, instance binding, or both. + ```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; + } + ``` + +The first operand 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. ``` -class A { - fn F[me: Self](); - fn G(); - var v: i32; - class B {}; +interface Printable { + fn Print[me: Self](); } -fn H(a: A) { - // ✅ OK, calls `A.F` with `me` initialized by `a`. - a.F(); - - // ✅ OK, same as above. - var bound_f: auto = a.F; - bound_f(); - - // ✅ OK, calls `A.G`. - A.G(); - // ✅ OK, evaluates expression `a` then calls `A.G`. - a.G(); - - // ❌ Error: name of instance member `A.v` can only be used in a - // member access or alias. - A.v = 1; - // ✅ OK - a.v = 1; - - // ✅ OK - let T:! Type = A.B; - // ❌ Error: value of `:!` binding is not constant because it - // refers to local variable `a`. - let U:! Type = a.B; -} - -interface I { - fn J[me: Self](); -} -impl A as I { - fn J[me: Self]() {} +external impl i32 as Printable { + fn Print[me: Self](); } -fn K(a: A) { - // ✅ OK: `I.J` is the interface member. - // `A.(I.J)` is the corresponding member of the `impl`. - // `a.(A.(I.J))` is a bound member function naming that member. - a.(A.(I.J))(); - - // ✅ Same as above, `a.(I.J)` is interpreted as `a.(A.(I.J))()` +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 interpreted as + // `n.(i32.(Printable.Print))()` // because `a` does not evaluate to a type. Performs impl lookup // and then instance binding. - a.(I.J)(); + n.(Printable.Print)(); } -// ✅ OK, member `J` of interface I. -alias X1 = I.J; +// ✅ OK, member `Print` of interface `Printable`. +alias X1 = Printable.Print; // ❌ Error, indirect access doesn't perform impl lookup or instance binding. -alias X2 = I.(I.J); -// ✅ OK, member `J` of `impl A as I`. -alias X3 = A.(I.J); +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 = A.(A.(I.J)); +alias X4 = i32.(i32.(Printable.Print)); ``` ## Precedence and associativity @@ -345,7 +409,7 @@ class A { interface B { fn F(); } -impl A as B; +external impl A as B; fn Use(a: A) { // Calls member `F` of class `A.B`. diff --git a/proposals/p0989.md b/proposals/p0989.md index f868373ef631b..673891db60ec1 100644 --- a/proposals/p0989.md +++ b/proposals/p0989.md @@ -25,10 +25,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception We need syntaxes for a number of closely-related operations: -- Given an expression denoting a package, namespace, class, interface, etc. - and the name of one of its members, form an expression denoting the member. - In C++, this is spelled `Container::MemberName`. In many other languages, it - is spelled `Container.MemberName`. +- 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 @@ -48,10 +48,10 @@ where the member name depends on the type or value of the first operand. ## Background -C++ distinguishes between the first use case and the rest. Other languages, such -as Rust, Swift, C#, and so on, 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. +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: From ea19037d6ce5781bfa7253a4e2eecaef964ab3eb Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 21 Jan 2022 18:35:03 -0800 Subject: [PATCH 12/36] Add missing `external`. --- docs/design/expressions/member_access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 895ba315697bf..5ab76ed9f373e 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -187,7 +187,7 @@ parameters that are in scope at the point where the member name appears. ```carbon class Cowboy { fn Draw[me: Self](); } interface Renderable { fn Draw[me: Self](); } -impl Cowboy as 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(); } From 0cb68a79c71731fda0198cb90cc005fb79eee8b5 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 21 Jan 2022 18:36:17 -0800 Subject: [PATCH 13/36] Add another couple of `external`s. --- docs/design/expressions/member_access.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 5ab76ed9f373e..c75ddcf386d2d 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -202,7 +202,7 @@ fn Draw(c: Cowboy) { } class RoundWidget { - impl as Renderable { + external impl as Renderable { fn Draw[me: Self](); } alias Draw = Renderable.Draw; @@ -210,7 +210,7 @@ class RoundWidget { class SquareWidget { fn Draw[me: Self]() {} - impl as Renderable { + external impl as Renderable { alias Draw = Self.Draw; } } From f7f78d2167fb1e13a6d5e78e4324e71bac9e5035 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 21 Jan 2022 18:38:11 -0800 Subject: [PATCH 14/36] Address review comment. --- docs/design/expressions/member_access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index c75ddcf386d2d..844cb26f96c96 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -425,7 +425,7 @@ Member access has lower precedence than primary expressions, and higher precedence than all other expression forms. ``` -// ✅ OK, `*` has lower precedence than `.`. +// ✅ 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; From 5ad6bc33adb5d9e0f8240fe731496380b20738b3 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 24 Jan 2022 14:33:10 -0800 Subject: [PATCH 15/36] Summarize member access in a table and use more meaningful names in some additional examples. --- docs/design/expressions/member_access.md | 87 ++++++++++++++---------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 844cb26f96c96..be8524494c18d 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -58,19 +58,19 @@ 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 N; -fn N.F() {} +namespace MyNamespace; +fn MyNamespace.Fn() {} // ✅ OK, can alias a namespace. -alias M = N; -fn G() { M.F(); } +alias MyNS = MyNamespace; +fn CallFn() { MyNS.Fn(); } // ❌ Error: a namespace is not a value. -let M2:! auto = N; +let MyNS2:! auto = MyNamespace; -fn H() { +fn CallFn2() { // ❌ Error: cannot perform indirect member access into a namespace. - N.(N.F)(); + MyNamespace.(MyNamespace.Fn)(); } ``` @@ -112,33 +112,37 @@ member being accessed. For example: ``` -interface I { - fn F[me: Self](); +interface Printable { + fn Print[me: Self](); } -class A { +external impl i32 as Printable; +class Point { var x: i32; - impl as I { - fn F[me: Self]() {} - } + var y: i32; + // Internal impl injects the name `Print` into class `Point`. + impl as Printable; } -fn Test(a: A) { - // ✅ OK, `x` found in type of `a`, namely `A`. - a.x = 1; - // ✅ OK, `x` found in the type `A`. - a.(A.x) = 1; - - // ✅ OK, `F` found in type of `a`, namely `A`. - a.F(); - // ✅ OK, `F` found in the type `I`. - a.(I.F)(); + +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 GenericTest[T: I](a: T) { +fn GenericPrint[T: Printable](a: T) { // ✅ OK, type of `a` is the type parameter `T`; - // `F` found in the type of `T`, namely `I`. - a.F(); + // `Print` found in the type of `T`, namely `Printable`. + a.Print(); } -fn CallGenericTest(a: A) { - GenericTest(a); +fn CallGenericPrint(p: Point) { + GenericPrint(p); } ``` @@ -249,6 +253,19 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { A member `M` is accessed within a value `V` as follows: +| Left of `.` | Right of `.` | Result | +| ------------ | -------------------------------- | ------------------------------------------------------------------------- | +| Non-type | Member of type | Bound member of the object | +| Non-type | Non-instance member of interface | The member of the matching `impl` | +| Non-type | Instance member of interface | Bound member of the object referring to the member of the matching `impl` | +| Type | Member of type | The member of the type | +| Type | Member of interface | The member of the matching `impl` | +| Type-of-type | Member of interface | The member of the interface | + +Any other case is an error. + +In more detail, member access consists of the following steps: + - _`impl` lookup:_ If `M` is a member of an interface `I` and `V` does not evaluate to a type-of-type, then `M` is replaced by the corresponding member of an implementation of `I`. @@ -314,17 +331,17 @@ A member `M` is accessed within a value `V` as follows: `me` parameter initialized by `V`. ```carbon - class C { - fn M[addr me: Self*](n: i32); + class Blob { + fn Mutate[addr me: Self*](n: i32); } - fn F(c: C) { - // ✅ OK, forms bound method `(c.M)` and calls it. - // This calls `C.M` with `me` initialized by `&c` + 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`. - c.M(5); + (*p).Mutate(5); // ✅ OK, same as above. - var bound_m: auto = c.M; + var bound_m: auto = (*p).Mutate; bound_m(5); } ``` From 6120907e669ed9c9e3656c102735a929c3b05b00 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 24 Jan 2022 15:38:14 -0800 Subject: [PATCH 16/36] Flesh out "alternatives considered". --- docs/design/expressions/member_access.md | 4 +- proposals/p0989.md | 125 ++++++++++++++++++++++- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index be8524494c18d..041f6ee95c3a5 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -450,9 +450,11 @@ var n: i32 = 1 + X.Y; ## Alternatives considered -- [Constrained template name lookup alternatives](https://github.com/carbon-language/carbon-lang/issues/949) +- [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) ## 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) diff --git a/proposals/p0989.md b/proposals/p0989.md index 673891db60ec1..9506e79f704f9 100644 --- a/proposals/p0989.md +++ b/proposals/p0989.md @@ -18,6 +18,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [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) @@ -143,4 +145,125 @@ See ## Alternatives considered -- [Question for leads: constrained template name lookup](https://github.com/carbon-language/carbon-lang/issues/949) +### 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 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. From aa8d643d3675a7dedc8d35db1ec0fd47a6484380 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 25 Jan 2022 12:03:15 -0800 Subject: [PATCH 17/36] Apply suggestions from code review Co-authored-by: josh11b --- docs/design/expressions/member_access.md | 4 ++-- proposals/p0989.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 041f6ee95c3a5..5c0925cd8507a 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -221,10 +221,10 @@ class SquareWidget { fn DrawWidget(r: RoundWidget, s: SquareWidget) { // ✅ OK, lookup in type and in type-of-type find the same entity. - UseTemplate(r); + DrawTemplate(r); // ✅ OK, lookup in type and in type-of-type find the same entity. - UseTemplate(s); + DrawTemplate(s); // ✅ OK, found in type. r.Draw(); diff --git a/proposals/p0989.md b/proposals/p0989.md index 9506e79f704f9..2e8c538ffdff4 100644 --- a/proposals/p0989.md +++ b/proposals/p0989.md @@ -248,7 +248,7 @@ Of these rules: 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 rules + 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 From 0652f2766170ccffcaea2764fd07d0320bbb1b31 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 25 Jan 2022 14:16:10 -0800 Subject: [PATCH 18/36] Missing update `a` -> `n` when switching example to nicer names. Co-authored-by: josh11b --- docs/design/expressions/member_access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 5c0925cd8507a..41bdd2bf585e1 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -398,7 +398,7 @@ fn MemberAccess(n: i32) { // ✅ Same as above, `n.(Printable.Print)` is interpreted as // `n.(i32.(Printable.Print))()` - // because `a` does not evaluate to a type. Performs impl lookup + // because `n` does not evaluate to a type. Performs impl lookup // and then instance binding. n.(Printable.Print)(); } From eeee8ee25a77721358c368b1272f8a9a91c829aa Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 25 Jan 2022 17:42:05 -0800 Subject: [PATCH 19/36] Restructure: add better summary and overview at the start and explain the steps before we go into detail. --- docs/design/expressions/member_access.md | 356 ++++++++++++++--------- 1 file changed, 223 insertions(+), 133 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 41bdd2bf585e1..2c0525680fd1e 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -11,10 +11,14 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents - [Overview](#overview) -- [Package and namespace members](#package-and-namespace-members) -- [Lookup within values](#lookup-within-values) - - [Templates and generics](#templates-and-generics) -- [Member access](#member-access) +- [Member resolution](#member-resolution) + - [Package and namespace members](#package-and-namespace-members) + - [Lookup within values](#lookup-within-values) + - [Templates and generics](#templates-and-generics) +- [`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) @@ -32,23 +36,73 @@ by a period. The name is found within a contextually-determined entity: 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. + +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_ `.` `(` - _member-access-expression_ `)` +- _member-access-expression_ ::= _expression_ `.` `(` _expression_ `)` + +The following kinds of member access expressions are supported: + +| Left of `.` | Right of `.` | Result | +| ------------ | -------------------------------- | ------------------------------------------------------------------------- | +| Non-type | Non-instance member of type | The member of the type | +| Non-type | Instance member of type | Bound member of the object | +| Non-type | Non-instance member of interface | The member of the matching `impl` | +| Non-type | Instance member of interface | Bound member of the object referring to the member of the matching `impl` | +| Type | Member of type | The member of the type | +| Type | Member of interface | The member of the matching `impl` | +| Type-of-type | Member of interface | The member of the interface | +| Namespace | Member of namespace | The member of the namespace | +| Package | Member of package | The member of the package | + +```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; +} -The meaning of a qualified name in a member access expression depends on the -first operand, which can be: +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 [package or namespace name](#package-and-namespace-members). -- A [value of some type](#lookup-within-values). +A member access expression is processed using the following steps: -## Package and namespace members +- First, the name 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 @@ -74,7 +128,7 @@ fn CallFn2() { } ``` -## Lookup within values +### Lookup within values When the first operand is not a package or namespace name, there are three remaining cases we wish to support: @@ -106,8 +160,9 @@ For a direct member access, the word is looked up in the following types: The results of these lookups are combined. If more than one distinct entity is found, the qualified name is invalid. -For an indirect member access, the second operand is evaluated to determine the -member being accessed. +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: @@ -146,10 +201,7 @@ fn CallGenericPrint(p: Point) { } ``` -The resulting member is then [accessed](#member-access) within the value denoted -by the first operand. - -### Templates and generics +#### 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 @@ -249,139 +301,177 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { } ``` -## Member access +## `impl` lookup -A member `M` is accessed within a value `V` as follows: +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. -| Left of `.` | Right of `.` | Result | -| ------------ | -------------------------------- | ------------------------------------------------------------------------- | -| Non-type | Member of type | Bound member of the object | -| Non-type | Non-instance member of interface | The member of the matching `impl` | -| Non-type | Instance member of interface | Bound member of the object referring to the member of the matching `impl` | -| Type | Member of type | The member of the type | -| Type | Member of interface | The member of the matching `impl` | -| Type-of-type | Member of interface | The member of the interface | +```carbon +interface Addable { + // #1 + fn Add[me: Self](other: Self) -> Self; +} +struct Integer { + impl as Addable { + // #2 + fn Add[me: Self](other: Self) -> Self; + } +} +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 #2. + // Finally, instance binding will be performed as described later. + return a.Add(b); +} +``` -Any other case is an error. +The type `T` that is expected to implement `I` depends on the first operand of +the member access expression, `V`: -In more detail, member access consists of the following steps: +- 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); + ``` -- _`impl` lookup:_ If `M` is a member of an interface `I` and `V` does not - evaluate to a type-of-type, then `M` is replaced by the corresponding member - of an implementation of `I`. +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. - The type `T` that is expected to implement `I` is `V` if `V` can be - evaluated and evaluates to a type, and `T` is the type of `V` otherwise. The - appropriate `impl T as I` implementation is located. `M` is replaced by the - member of that `impl` that corresponds to `M`. +`M` is replaced by the member of the `impl` that corresponds to `M`. - ```carbon - interface I { - // #1 - fn F[me: Self]() {} - } - class C { - impl as I { - // #2 - fn F[me: C]() {} - } - } +```carbon +interface I { + // #1 + fn F[me: Self]() {} +} +class C { + impl as I { + // #2 + fn F[me: C]() {} + } +} - // `M` is `I.F` and `V` is `I`. Because `V` is a type-of-type, - // `impl` lookup is not performed, and the alias binds to #1. - alias A1 = I.F; +// `M` is `I.F` and `V` is `I`. Because `V` is a type-of-type, +// `impl` lookup is not performed, and the alias binds to #1. +alias A1 = I.F; - // `M` is `I.F` and `V` is `C`. Because `V` is a type, `T` is `C`. - // `impl` lookup is performed, and the alias binds to #2. - alias A2 = C.F; - ``` +// `M` is `I.F` and `V` is `C`. Because `V` is a type, `T` is `C`. +// `impl` lookup is performed, and the alias binds to #2. +alias A2 = C.F; +``` - Instance binding may also apply if the member is an instance member. +[Instance binding](#instance-binding) may also apply if the member is an +instance member. + +```carbon +var c: C; +// `M` is `I.F` and `V` is `c`. 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(); +``` + +## 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 c: C; - // `M` is `I.F` and `V` is `c`. 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(); + 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; ``` -- _Instance binding:_ If the member is an instance member -- a field or a - method -- and `V` does not evaluate to a type, then: - - - 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, `V` is converted to the recipient type, as follows. First, - if the method declares its recipient type with `addr`, then `V` is - replaced by `&V`. Then, `V` is implicitly converted to the declared `me` - type. - - 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 `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. - var bound_m: auto = (*p).Mutate; - bound_m(5); - } - ``` - -- If instance binding is not performed, the result is the member, but - evaluating the member access expression still evaluates `V`. An expression - that names an instance member can only be used as the second operand of a - member access or as the target of an `alias` declaration. +- For a method, `V` is converted to the recipient type, as follows. First, if + the method declares its recipient type with `addr`, then `V` is replaced by + `&V`. Then, `V` is implicitly converted to the declared `me` type. + + 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 `V`. ```carbon - class C { - fn StaticMethod(); - var field: i32; - class Nested {} + class Blob { + fn Mutate[addr me: Self*](n: i32); } - 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; + 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. + var bound_m: auto = (*p).Mutate; + bound_m(5); } ``` -The first operand 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. +## 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 a 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 { From 70903f8c2c0bbf449405aa71f74d6c77ae7891e4 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 25 Jan 2022 18:10:17 -0800 Subject: [PATCH 20/36] Attempt to clarify in response to review comments. --- docs/design/expressions/member_access.md | 64 +++++++++++++++--------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 2c0525680fd1e..d47d2613b7f95 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -47,19 +47,7 @@ or an _indirect_ member access of the form: - _member-access-expression_ ::= _expression_ `.` `(` _expression_ `)` -The following kinds of member access expressions are supported: - -| Left of `.` | Right of `.` | Result | -| ------------ | -------------------------------- | ------------------------------------------------------------------------- | -| Non-type | Non-instance member of type | The member of the type | -| Non-type | Instance member of type | Bound member of the object | -| Non-type | Non-instance member of interface | The member of the matching `impl` | -| Non-type | Instance member of interface | Bound member of the object referring to the member of the matching `impl` | -| Type | Member of type | The member of the type | -| Type | Member of interface | The member of the matching `impl` | -| Type-of-type | Member of interface | The member of the interface | -| Namespace | Member of namespace | The member of the namespace | -| Package | Member of package | The member of the package | +For example: ```carbon package Widgets api; @@ -97,6 +85,21 @@ A member access expression is processed using the following steps: - If [instance binding is not performed](#non-instance-members), the result is `M`. +The following kinds of member access expressions are supported, depending on the +first operand of the `.` and the member that the second operand resolves to: + +| Left of `.` | Right of `.` | Result | +| ------------ | -------------------------------- | ------------------------------------------------------------------------- | +| Non-type | Non-instance member of type | The member of the type | +| Non-type | Instance member of type | Bound member of the object | +| Non-type | Non-instance member of interface | The member of the matching `impl` | +| Non-type | Instance member of interface | Bound member of the object referring to the member of the matching `impl` | +| Type | Member of type | The member of the type | +| Type | Member of interface | The member of the matching `impl` | +| Type-of-type | Member of interface | The member of the interface | +| Namespace | Member of namespace | The member of the namespace | +| Package | Member of package | The member of the package | + ## Member resolution The process of _member resolution_ determines which member `M` a member access @@ -242,7 +245,10 @@ parameters that are in scope at the point where the member name appears. ```carbon class Cowboy { fn Draw[me: Self](); } -interface Renderable { fn Draw[me: Self](); } +interface Renderable { + // #1 + 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(); } @@ -259,12 +265,15 @@ fn Draw(c: Cowboy) { 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; @@ -282,21 +291,30 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { r.Draw(); s.Draw(); - // ✅ OK, inner member access resolves `RoundWidget.Draw` to - // the member `Draw` of `impl RoundWidget as Renderable`, - // outer member access forms a bound member function. + // ✅ 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`. r.(RoundWidget.Draw)(); - // ✅ OK, inner member access names `SquareWidget.Draw`, - // outer member access forms a bound member function. + // ✅ 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, can't call `Draw[me: SquareWidget]()` on `RoundWidget` object. + // ❌ 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, inner member access resolves `RoundWidget.Draw` to - // the member `Draw` of `impl RoundWidget as Renderable`; - // can't call `Draw[me: RoundWidget]()` on `SquareWidget` object. + // ❌ 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)(); } ``` From 735abac77ef76e59734f9aa800df971fdf3aadd6 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 26 Jan 2022 13:16:24 -0800 Subject: [PATCH 21/36] Reflow to appease prettier. --- proposals/p0989.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/p0989.md b/proposals/p0989.md index 2e8c538ffdff4..14607ba6a50e4 100644 --- a/proposals/p0989.md +++ b/proposals/p0989.md @@ -248,9 +248,9 @@ Of these rules: 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_. + 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 From c6afd28bca2ee00ed381a1517552bebb8afcfe7c Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 26 Jan 2022 14:49:58 -0800 Subject: [PATCH 22/36] Apply suggestions from code review Co-authored-by: josh11b --- docs/design/expressions/member_access.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index d47d2613b7f95..7514d5b0bb55b 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -28,7 +28,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## 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: +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 @@ -194,7 +194,7 @@ fn PrintPointTwice() { // ✅ OK, `Print` found in the type `Printable`. p.(Printable.Print)(); } -fn GenericPrint[T: Printable](a: T) { +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(); From 3ed3a43e9aa05d6ea9d4b2f904302d899686b065 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 26 Jan 2022 14:50:43 -0800 Subject: [PATCH 23/36] Respond to review comments. --- docs/design/expressions/member_access.md | 172 +++++++++++++++++------ 1 file changed, 129 insertions(+), 43 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 7514d5b0bb55b..76644740a977d 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -153,6 +153,11 @@ 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. @@ -246,7 +251,6 @@ parameters that are in scope at the point where the member name appears. ```carbon class Cowboy { fn Draw[me: Self](); } interface Renderable { - // #1 fn Draw[me: Self](); } external impl Cowboy as Renderable { fn Draw[me: Self](); } @@ -265,7 +269,6 @@ fn Draw(c: Cowboy) { class RoundWidget { external impl as Renderable { - // #2 fn Draw[me: Self](); } // `Draw` names the member of the `Renderable` interface. @@ -273,7 +276,6 @@ class RoundWidget { } class SquareWidget { - // #3 fn Draw[me: Self]() {} external impl as Renderable { alias Draw = Self.Draw; @@ -290,32 +292,6 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { // ✅ OK, found in type. r.Draw(); s.Draw(); - - // ✅ 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`. - 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)(); } ``` @@ -332,18 +308,38 @@ expression. interface Addable { // #1 fn Add[me: Self](other: Self) -> Self; + // #2 + fn Sum[Seq:! Iterable where .ValueType = Self](seq: Seq) -> Self { + // ... + } } + struct Integer { impl as Addable { - // #2 + // #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 #2. + // 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); } ``` @@ -377,9 +373,10 @@ the argument for the template parameter is known. interface I { // #1 fn F[me: Self]() {} + let N:! i32; } class C { - impl as I { + impl as I where .N = 5 { // #2 fn F[me: C]() {} } @@ -389,9 +386,17 @@ class C { // `impl` lookup is not performed, and the alias binds to #1. alias A1 = I.F; -// `M` is `I.F` and `V` is `C`. Because `V` is a type, `T` is `C`. -// `impl` lookup is performed, and the alias binds to #2. +// `M` is `I.F` and `V` is `C`. 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 = {}; + +// `M` is `I.N` and `V` is `c`. 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 @@ -405,6 +410,87 @@ var c: C; 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 below. + 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:! Type](v: Vector(T)) { + for (var w: T in v) { + // ✅ OK if `T` implements `Renderable; calls `Renderable.Draw`. + // Unqualified lookup for `Draw` does not perform `impl` lookup. + 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 @@ -423,13 +509,13 @@ value other than a type, then _instance binding_ is performed, as follows: dims.height = 3; ``` -- For a method, `V` is converted to the recipient type, as follows. First, if - the method declares its recipient type with `addr`, then `V` is replaced by - `&V`. Then, `V` is implicitly converted to the declared `me` type. +- 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: - 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 `V`. + - If the method declares its `me` parameter with `addr`, the recipient + argument is `&V`. + - Otherwise, the recipient argument is `V`. ```carbon class Blob { @@ -442,7 +528,7 @@ value other than a type, then _instance binding_ is performed, as follows: (*p).Mutate(5); // ✅ OK, same as above. - var bound_m: auto = (*p).Mutate; + let bound_m: auto = (*p).Mutate; bound_m(5); } ``` @@ -454,8 +540,8 @@ 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 a member access or as -the target of an `alias` declaration. +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 { From a241782515ffd807e8ed8ab430691b4e44c97aaf Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 26 Jan 2022 18:26:22 -0800 Subject: [PATCH 24/36] Add elided word back for clarity. --- docs/design/expressions/member_access.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 76644740a977d..7cdffd00a68ac 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -283,10 +283,10 @@ class SquareWidget { } fn DrawWidget(r: RoundWidget, s: SquareWidget) { - // ✅ OK, lookup in type and in type-of-type find the same entity. + // ✅ OK, lookup in type and lookup in type-of-type find the same entity. DrawTemplate(r); - // ✅ OK, lookup in type and in type-of-type find the same entity. + // ✅ OK, lookup in type and lookup in type-of-type find the same entity. DrawTemplate(s); // ✅ OK, found in type. From 0d169e4a5a3ef6cc7cb547676aba8f56a143690d Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 26 Jan 2022 18:44:08 -0800 Subject: [PATCH 25/36] Attempt to clarify that lookup never considers the values of in-scope generic parameters. --- docs/design/expressions/member_access.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 7cdffd00a68ac..a277449cec1db 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -245,8 +245,10 @@ values of any generic parameters are still unknown. The lookup results from these two contexts are combined, and if more than one distinct entity is found, the qualified name is invalid. -The lookup for a member name never considers the values of any generic -parameters that are in scope at the point where the member name appears. +**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](); } @@ -271,7 +273,6 @@ class RoundWidget { external impl as Renderable { fn Draw[me: Self](); } - // `Draw` names the member of the `Renderable` interface. alias Draw = Renderable.Draw; } From 05c41eff451b2c8339da7051c935c82433adfb09 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Jan 2022 16:13:06 -0800 Subject: [PATCH 26/36] Apply suggestions from code review Co-authored-by: josh11b --- docs/design/expressions/member_access.md | 4 ++-- proposals/p0989.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index a277449cec1db..94e17cc170534 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -315,7 +315,7 @@ interface Addable { } } -struct Integer { +class Integer { impl as Addable { // #3 fn Add[me: Self](other: Self) -> Self; @@ -592,7 +592,7 @@ fn MemberAccess(n: i32) { n.(i32.(Printable.Print))(); // ✅ Same as above, `n.(Printable.Print)` is interpreted as - // `n.(i32.(Printable.Print))()` + // `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)(); diff --git a/proposals/p0989.md b/proposals/p0989.md index 14607ba6a50e4..4853b09146a05 100644 --- a/proposals/p0989.md +++ b/proposals/p0989.md @@ -102,7 +102,7 @@ class Potato { fn Hash[me: Self](); alias HashValue = Hashable.HashValue; } -extern impl Potato as Hashable where .HashValue = u32 { +external impl Potato as Hashable where .HashValue = u32 { // ... } fn H[template T:! Hashable](x: T, s: HashState) { From babfee7f4ad8ca90adfd59bbb01a855fe6e26d98 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Jan 2022 16:13:35 -0800 Subject: [PATCH 27/36] Clarify that we're not giving an example of a rewrite rule, just an equivalence. --- docs/design/expressions/member_access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 94e17cc170534..b20727fb3d360 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -591,7 +591,7 @@ fn MemberAccess(n: i32) { // `n.(i32.(Printable.Print))` is a bound member function naming that member. n.(i32.(Printable.Print))(); - // ✅ Same as above, `n.(Printable.Print)` is interpreted as + // ✅ 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. From 4d9c39c2cd7e8ad33c94084f5a2d2ccda0c00015 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Jan 2022 16:33:54 -0800 Subject: [PATCH 28/36] Response to review comments. --- docs/design/expressions/member_access.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index b20727fb3d360..13932e6d493f7 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -383,17 +383,17 @@ class C { } } -// `M` is `I.F` and `V` is `I`. Because `V` is a type-of-type, +// `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; -// `M` is `I.F` and `V` is `C`. Because `V` is a type, `impl` +// `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 = {}; -// `M` is `I.N` and `V` is `c`. Because `V` is a non-type, `impl` +// `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. @@ -405,7 +405,7 @@ instance member. ```carbon var c: C; -// `M` is `I.F` and `V` is `c`. Because `V` is not a type, `T` is the +// `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(); @@ -443,7 +443,7 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { // 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 below. + // calls #2 on `r`, as described in "Instance binding". r.(RoundWidget.Draw)(); // ✅ OK: In the inner member access, the name `Draw` resolves to the @@ -469,10 +469,12 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { base class WidgetBase { // ✅ OK, even though `WidgetBase` does not implement `Renderable`. alias Draw = Renderable.Draw; - fn DrawAll[T:! Type](v: Vector(T)) { + fn DrawAll[T:! Renderable](v: Vector(T)) { for (var w: T in v) { - // ✅ OK if `T` implements `Renderable; calls `Renderable.Draw`. + // ✅ OK, `T` is known to implement `Renderable`. // Unqualified lookup for `Draw` does not perform `impl` lookup. + // Indirect member access expression performs `impl` lookup into + // `impl T as Renderable`. w.(Draw)(); // ❌ Error: `Self.Draw` performs `impl` lookup, which fails // because `WidgetBase` does not implement `Renderable`. From b9427a486d4b3d83496b72a404e6083bc115457a Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Jan 2022 16:44:03 -0800 Subject: [PATCH 29/36] Remove table of cases; it doesn't seem to pull its weight. --- docs/design/expressions/member_access.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 13932e6d493f7..dc341e0043847 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -85,21 +85,6 @@ A member access expression is processed using the following steps: - If [instance binding is not performed](#non-instance-members), the result is `M`. -The following kinds of member access expressions are supported, depending on the -first operand of the `.` and the member that the second operand resolves to: - -| Left of `.` | Right of `.` | Result | -| ------------ | -------------------------------- | ------------------------------------------------------------------------- | -| Non-type | Non-instance member of type | The member of the type | -| Non-type | Instance member of type | Bound member of the object | -| Non-type | Non-instance member of interface | The member of the matching `impl` | -| Non-type | Instance member of interface | Bound member of the object referring to the member of the matching `impl` | -| Type | Member of type | The member of the type | -| Type | Member of interface | The member of the matching `impl` | -| Type-of-type | Member of interface | The member of the interface | -| Namespace | Member of namespace | The member of the namespace | -| Package | Member of package | The member of the package | - ## Member resolution The process of _member resolution_ determines which member `M` a member access From 27335b766e3a614eb5068cd086d7ba7c82420a7e Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 1 Feb 2022 13:06:03 -0800 Subject: [PATCH 30/36] Factor out wording about combining lookup results and say that it takes impl lookup into account. --- docs/design/expressions/member_access.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index dc341e0043847..ec82561c8df21 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -15,6 +15,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [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) @@ -150,8 +151,7 @@ For a direct member access, the word is looked up in the following types: - 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. If more than one distinct entity is -found, the qualified name is invalid. +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 @@ -227,8 +227,7 @@ fn G[template T:! Type](x: TemplateWrapper(T)) -> T { 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, and if more than one distinct entity is found, -the qualified name is invalid. +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 @@ -281,6 +280,14 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { } ``` +#### 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 From 2c09a73113d79a9f8573a6a1873e82d85965d110 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 1 Feb 2022 17:48:39 -0800 Subject: [PATCH 31/36] Expand explanation in example. Co-authored-by: josh11b --- docs/design/expressions/member_access.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index ec82561c8df21..64ae67f91b15d 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -463,10 +463,13 @@ base class WidgetBase { alias Draw = Renderable.Draw; fn DrawAll[T:! Renderable](v: Vector(T)) { for (var w: T in v) { - // ✅ OK, `T` is known to implement `Renderable`. - // Unqualified lookup for `Draw` does not perform `impl` lookup. - // Indirect member access expression performs `impl` lookup into - // `impl T as Renderable`. + // ✅ 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)(); w.(Draw)(); // ❌ Error: `Self.Draw` performs `impl` lookup, which fails // because `WidgetBase` does not implement `Renderable`. From 207347b4dac3bea4088bf4299bf7fd9e2290e2b9 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 2 Feb 2022 12:31:28 -0800 Subject: [PATCH 32/36] Add discussion of alternative interpretation of `Type.Interface`. --- docs/design/expressions/member_access.md | 1 + proposals/p0989.md | 77 ++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 64ae67f91b15d..b4b700d00be10 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -644,6 +644,7 @@ var n: i32 = 1 + X.Y; - [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 diff --git a/proposals/p0989.md b/proposals/p0989.md index 4853b09146a05..4c5cf0bcd9005 100644 --- a/proposals/p0989.md +++ b/proposals/p0989.md @@ -20,6 +20,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [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) @@ -267,3 +268,79 @@ 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. From 751f02ecbf83047905ca5bb71627967a9d86c89b Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 2 Feb 2022 12:56:43 -0800 Subject: [PATCH 33/36] Fix trailing whitespace. --- docs/design/expressions/member_access.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index b4b700d00be10..91f1bc1c38ebb 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -467,9 +467,8 @@ base class WidgetBase { // 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 + // `Renderable`. Finally, the member function is bound to `w` as // described in "Instance binding". - w.(Draw)(); w.(Draw)(); // ❌ Error: `Self.Draw` performs `impl` lookup, which fails // because `WidgetBase` does not implement `Renderable`. From a3bddecc6714340144ebca65ef98156e34d2761f Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 1 Mar 2022 18:31:42 -0800 Subject: [PATCH 34/36] Respond to review comments. --- docs/design/expressions/member_access.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 91f1bc1c38ebb..1d3d18b288b11 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -74,7 +74,7 @@ fn GrowSomeCogs() { A member access expression is processed using the following steps: -- First, the name or parenthesized expression to the right of the `.` is +- 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 @@ -102,18 +102,18 @@ operand of a member access or as the target of an `alias` declaration. ``` namespace MyNamespace; -fn MyNamespace.Fn() {} +fn MyNamespace.MyFunction() {} // ✅ OK, can alias a namespace. alias MyNS = MyNamespace; -fn CallFn() { MyNS.Fn(); } +fn CallMyFunction() { MyNS.MyFunction(); } // ❌ Error: a namespace is not a value. let MyNS2:! auto = MyNamespace; -fn CallFn2() { +fn CallMyFunction2() { // ❌ Error: cannot perform indirect member access into a namespace. - MyNamespace.(MyNamespace.Fn)(); + MyNamespace.(MyNamespace.MyFunction)(); } ``` @@ -214,6 +214,7 @@ class TemplateWrapper(template T:! Type) { var field: T; } fn G[template T:! Type](x: TemplateWrapper(T)) -> T { + // 🤷 Not yet decided. return x.field; } ``` @@ -302,7 +303,7 @@ interface Addable { // #1 fn Add[me: Self](other: Self) -> Self; // #2 - fn Sum[Seq:! Iterable where .ValueType = Self](seq: Seq) -> Self { + default fn Sum[Seq:! Iterable where .ValueType = Self](seq: Seq) -> Self { // ... } } @@ -365,7 +366,7 @@ the argument for the template parameter is known. ```carbon interface I { // #1 - fn F[me: Self]() {} + default fn F[me: Self]() {} let N:! i32; } class C { From 3f80189d734eb58e2886b11629b1a04c6245618c Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 2 Mar 2022 12:13:15 -0800 Subject: [PATCH 35/36] Attempt to fix mermaid error: don't use underscore in node name. --- docs/design/expressions/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/design/expressions/README.md b/docs/design/expressions/README.md index 7411cb2c5f892..82322cb350f34 100644 --- a/docs/design/expressions/README.md +++ b/docs/design/expressions/README.md @@ -57,12 +57,12 @@ graph BT braces["{...}"] click braces "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#literals" - unqualified_name["x"] - click unqualified_name "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/README.md#unqualified-names" + unqualifiedName["x"] + click unqualifiedName "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/README.md#unqualified-names" - member_access>"x.y
+ memberAccess>"x.y
x.(...)"] - click member_access "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/member_access.md" + 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" @@ -89,8 +89,8 @@ graph BT expressionEnd["x;"] - member_access -> parens & braces & unqualified_name - as & not --> member_access + memberAccess -> parens & braces & unqualifiedName + as & not --> memberAccess comparison --> as and & or --> comparison & not if & expressionEnd --> and & or From 3ae955c39dece2b976b27f28062338a251bde260 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 2 Mar 2022 12:14:28 -0800 Subject: [PATCH 36/36] Fix '->' typo in mermaid diagram. --- docs/design/expressions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/expressions/README.md b/docs/design/expressions/README.md index 82322cb350f34..7ce3f78bb3825 100644 --- a/docs/design/expressions/README.md +++ b/docs/design/expressions/README.md @@ -89,7 +89,7 @@ graph BT expressionEnd["x;"] - memberAccess -> parens & braces & unqualifiedName + memberAccess --> parens & braces & unqualifiedName as & not --> memberAccess comparison --> as and & or --> comparison & not