diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 356da99dbf463..0106d2d6e2007 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -15,7 +15,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Implementing interfaces](#implementing-interfaces) - [Implementing multiple interfaces](#implementing-multiple-interfaces) - [External impl](#external-impl) - - [Qualified member names](#qualified-member-names) + - [Qualified member names and compound member access](#qualified-member-names-and-compound-member-access) - [Access](#access) - [Generics](#generics) - [Return type](#return-type) @@ -188,9 +188,9 @@ This includes members of `ConvertibleToString` that are not explicitly named in the `impl` definition but have defaults. Whether the implementation is defined as [internal](terminology.md#internal-impl) or [external](terminology.md#external-impl), you may access the `ToString` function -for a `Song` value `s` by writing a -[qualified](terminology.md#qualified-and-unqualified-member-names) function -call, like `s.(ConvertibleToString.ToString)()`. +for a `Song` value `s` by a writing function call +[using the compound member access syntax with the qualified name](terminology.md#compound-member-access-using-qualified-names), +like `s.(ConvertibleToString.ToString)()`. If `Song` doesn't implement an interface or we would like to use a different implementation of that interface, we can define another type that also has the @@ -405,8 +405,9 @@ Carbon requires `impl`s defined in a different library to be `external` so that the API of `Point3` doesn't change based on what is imported. It would be particularly bad if two different libraries implemented interfaces with conflicting names that both affected the API of a single type. As a consequence -of this restriction, you can find all the names of direct (unqualified) members -of a type in the definition of that type. The only thing that may be in another +of this restriction, you can find all the names of direct members (those +available by [simple member access](terminology.md#simple-member-access)) of a +type in the definition of that type. The only thing that may be in another library is an `impl` of an interface. You might also use `external impl` to implement an interface for a type to avoid @@ -476,8 +477,10 @@ same library as the class? different files based on explicit configuration in that file. For example, we could support a declaration that a given interface or a given method of an interface is "in scope" for a particular type in this file. With that -declaration, the method could be called unqualified. This avoids most concerns -arising from name collisions between interfaces. It has a few downsides though: +declaration, the method could be called using +[simple member access](terminology.md#simple-member-access). This avoids most +concerns arising from name collisions between interfaces. It has a few downsides +though: - It increases variability between files, since the same type will have different APIs depending on these declarations. This makes it harder to @@ -495,12 +498,14 @@ Swift and Rust, we don't allow a type's API to be modified outside its definition. So in Carbon a type's API is consistent no matter what is imported, unlike Swift and Rust. -### Qualified member names +### Qualified member names and compound member access Given a value of type `Point3` and an interface `Vector` implemented for that type, you can access the methods from that interface using the member's -_qualified name_, whether or not the implementation is done externally with an -`external impl` declaration: +_qualified name_ using +[the compound member access syntax](terminology.md#simple-member-access), +whether or not the implementation is done externally with an `external impl` +declaration: ``` var p1: Point3 = {.x = 1.0, .y = 2.0}; @@ -581,7 +586,9 @@ present in the signature of the function to type check the body of Names are looked up in the body of `AddAndScaleGeneric` for values of type `T` in `Vector`. This means that `AddAndScaleGeneric` is interpreted as equivalent -to adding a `Vector` qualification to all unqualified member accesses of `T`: +to adding a `Vector` +[qualification](#qualified-member-names-and-compound-member-access) to replace +all simple member accesses of `T`: ``` fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: Double) -> T { @@ -785,7 +792,8 @@ signatures. Implementing `Vector` would not imply an implementation of An interface's name may be used in a few different contexts: - to define [an `impl` for a type](#implementing-interfaces), -- as a namespace name in [a qualified name](#qualified-member-names), and +- as a namespace name in + [a qualified name](#qualified-member-names-and-compound-member-access), and - as a [type-of-type](terminology.md#type-of-type) for [a generic type parameter](#generics). @@ -806,7 +814,8 @@ aliases for the corresponding qualified names inside `Vector` as a namespace. The requirements determine which types are values of a given type-of-type. The set of names in a type-of-type determines the API of a generic type value and -define the result of qualified member name lookup. +define the result of [member access](/docs/design/expressions/member_access.md) +into the type-of-type. This general structure of type-of-types holds not just for interfaces, but others described in the rest of this document. @@ -875,9 +884,8 @@ members of those interfaces. To declare a named constraint that includes other declarations for use with template parameters, use the `template` keyword before `constraint`. Method, associated type, and associated function requirements may only be declared -inside a `template constraint`. Note that a generic constraint ignores the -unqualified member names defined for a type, but a template constraint can -depend on them. +inside a `template constraint`. Note that a generic constraint ignores the names +of members defined for a type, but a template constraint can depend on them. There is an analogy between declarations used in a `constraint` and in an `interface` definition. If an `interface` `I` has (non-`alias`) declarations @@ -1036,8 +1044,8 @@ constraint { ``` Conflicts can be resolved at the call site using -[the qualified name syntax](#qualified-member-names), or by defining a named -constraint explicitly and renaming the methods: +[the compound member access syntax using qualified names](#qualified-member-names-and-compound-member-access), +or by defining a named constraint explicitly and renaming the methods: ``` constraint RenderableAndEndOfGame { @@ -1561,7 +1569,7 @@ adapter SongByTitle for Song { } ``` -or using qualified names: +or using qualified names with the compound member access syntax: ``` adapter SongByTitle for Song { @@ -1852,8 +1860,8 @@ external impl Window as DrawingContext { ... } An adapter can make that much more convenient by making a compatible type where the interface is [implemented internally](terminology.md#internal-impl). This avoids having to -[qualify](terminology.md#qualified-and-unqualified-member-names) each call to -methods in the interface. +[qualify](terminology.md#compound-member-access-using-qualified-names) each call +to methods in the interface. ``` adapter DrawInWindow for Window { @@ -2580,14 +2588,13 @@ fn Contains the `where` constraint means `CT.ElementType` must satisfy `Comparable` as well. However, inside the body of `Contains`, `CT.ElementType` will only act like the implementation of `Comparable` is [external](#external-impl). That is, items -from the `needles` container won't have an unqualified `Compare` method member, -but can still be implicitly converted to `Comparable` and can still call -`Compare` using the qualified member syntax, `needle.(Comparable.Compare)(elt)`. -The rule is that an `==` `where` constraint between two type variables does not -modify the set of unqualified member names of either type. (If you write +from the `needles` container won't directly have a `Compare` method member, but +can still be implicitly converted to `Comparable` and can still call `Compare` +using the compound member access syntax, `needle.(Comparable.Compare)(elt)`. The +rule is that an `==` `where` constraint between two type variables does not +modify the set of member names of either type. (If you write `where .ElementType = String` with a `=` and a concrete type, then -`.ElementType` is actually set to `String` including the complete unqualified -`String` API.) +`.ElementType` is actually set to `String` including the complete `String` API.) Note that `==` constraints are symmetric, so the previous declaration of `Contains` is equivalent to an alternative declaration where `CT` is declared @@ -2876,9 +2883,9 @@ This implied constraint is equivalent to the explicit constraint that each parameter and return type [is legal](#must-be-legal-type-argument-constraints). **Note:** These implied constraints affect the _requirements_ of a generic type -parameter, but not its _unqualified member names_. This way you can always look -at the declaration to see how name resolution works, without having to look up -the definitions of everything it is used as an argument to. +parameter, but not its _member names_. This way you can always look at the +declaration to see how name resolution works, without having to look up the +definitions of everything it is used as an argument to. **Limitation:** To limit readability concerns and ambiguity, this feature is limited to a single signature. Consider this interface declaration: diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index 3ed155ae16a02..4a4b35c2e49b8 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -20,7 +20,7 @@ pointers to other design documents that dive deeper into individual topics. - [Defining interfaces](#defining-interfaces) - [Contrast with templates](#contrast-with-templates) - [Implementing interfaces](#implementing-interfaces) - - [Qualified and unqualified access](#qualified-and-unqualified-access) + - [Accessing members of interfaces](#accessing-members-of-interfaces) - [Type-of-types](#type-of-types) - [Generic functions](#generic-functions) - [Deduced parameters](#deduced-parameters) @@ -90,8 +90,9 @@ Summary of how Carbon generics work: ["named constraints"](terminology.md#named-constraints). Named constraints can express requirements that multiple interfaces be implemented, and give you control over how name conflicts are handled. -- Alternatively, you may resolve name conflicts by using a qualified syntax to - directly call a function from a specific interface. +- Alternatively, you may resolve name conflicts by using the compound member + access syntax to directly call a function from a specific interface using a + qualified name. ## What are generics? @@ -216,16 +217,17 @@ external impl Song as Comparable { Implementations may be defined within the class definition itself or out-of-line. Implementations may optionally be start with the `external` keyword -to say the members of the interface are not unqualified members of the class. -Out-of-line implementations must be external. External implementations may be -defined in the library defining either the class or the interface. +to say the members of the interface are not members of the class. Out-of-line +implementations must be external. External implementations may be defined in the +library defining either the class or the interface. -#### Qualified and unqualified access +#### Accessing members of interfaces The methods of an interface implemented internally within the class definition -may be called with the ordinary unqualified member syntax. Methods of all -implemented interfaces may be called with the -[qualified member syntax](terminology.md#qualified-and-unqualified-member-names), +may be called with the +[simple member access syntax](terminology.md#simple-member-access). Methods of +all implemented interfaces may be called with the +[compound member access syntax using qualified names](terminology.md#compound-member-access-using-qualified-names), whether they are defined internally or externally. ``` @@ -235,7 +237,8 @@ song.Print(); // `Less` is defined in `Comparable`, which is implemented // externally for `Song` song.(Comparable.Less)(song); -// Can also call `Print` using the qualified syntax: +// Can also call `Print` using the compound access syntax, +// using the qualified name `Printable.Print`: song.(Printable.Print)(); ``` @@ -257,9 +260,8 @@ type is that it must implement the interface `Comparable`. A type-of-type also defines a set of names and a mapping to corresponding qualified names. Those names are used for -[unqualfied member lookup](terminology.md#qualified-and-unqualified-member-names) -in scopes where the value of the type is not known, such as when the type is a -generic parameter. +[simple member lookup](terminology.md#simple-member-access) in scopes where the +value of the type is not known, such as when the type is a generic parameter. You may combine interfaces into new type-of-types using [the `&` operator](#combining-interfaces) or @@ -325,8 +327,9 @@ differently because they are defined as generic, as long as you only refer to the names defined by [type-of-type](#type-of-types) for the type parameter. You may also refer to any of the methods of interfaces required by the -type-of-type using the [qualified syntax](#qualified-and-unqualified-access), as -shown in the following sections. +type-of-type using the +[compound member access syntax with the qualified member name](#accessing-members-of-interfaces), +as shown in the following sections. A function can have a mix of generic, template, and regular parameters. Likewise, it's allowed to pass a template or generic value to a generic or @@ -409,7 +412,7 @@ fn F[T:! Renderable & EndOfGame](game_state: T*) -> (i32, i32) { ``` Names with conflicts can be accessed using the -[qualified syntax](#qualified-and-unqualified-access). +[compound member access syntax](#accessing-members-of-interfaces). ``` fn BothDraws[T:! Renderable & EndOfGame](game_state: T*) { @@ -442,8 +445,8 @@ fn CallItAll[T:! Combined](game_state: T*, int winner) { game_state->Draw_EndOfGame(); } game_state->Draw_Renderable(); - // Can still use qualified syntax for names - // not defined in the named constraint + // Can still use compound member access syntax for + // names not defined in the named constraint return game_state->(Renderable.Center)(); } ``` diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 7974cd5dc73d6..6308a19c8d39f 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -30,7 +30,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Impls: Implementations of interfaces](#impls-implementations-of-interfaces) - [Internal impl](#internal-impl) - [External impl](#external-impl) -- [Qualified and unqualified member names](#qualified-and-unqualified-member-names) +- [Member access](#member-access) + - [Simple member access](#simple-member-access) + - [Compound member access using qualified names](#compound-member-access-using-qualified-names) - [Compatible types](#compatible-types) - [Subtyping and casting](#subtyping-and-casting) - [Coherence](#coherence) @@ -357,28 +359,51 @@ constraint as a way to implement all of the interfaces it requires. A type that implements an interface _internally_ has all the named members of the interface as named members of the type. This means that the members of the -interface may be accessed as either -[unqualified or qualified members](#qualified-and-unqualified-member-names). +interface are available by way of both +[simple member access and compound member access using qualified names](#member-access). ### External impl In contrast, a type that implements an interface _externally_ does not include the named members of the interface in the type. The members of the interface are -still implemented by the type, though, and so may be accessed using the -[qualified names](#qualified-and-unqualified-member-names) of those members. +still implemented by the type, though, and so may be accessed using +[compound member access using the qualified names](#compound-member-access-using-qualified-names) +of those members. -## Qualified and unqualified member names +## Member access -A qualified member includes both the name of the interface defining the member -and the name of the member. So if `String` implements `Comparable` which has a -`Less` method, and `s1` and `s2` are variables of type `String`, then the `Less` +There are two different kinds of member access: _simple_ and _compound_. There +is a [member access design document](/docs/design/expressions/member_access.md) +with the details, but a summary of how they apply to generics is given here. + +### Simple member access + +Simple member access has the from `object.member`, where `member` is a word +naming a member of `object`. This form may be used to access members of +interfaces [implemented internally](#internal-impl) by the type of `object`. + +If `String` implements `Printable` internally, then `s1.Print()` calls the +`Print` method of `Printable` using simple member access. In this case, the name +`Print` is used without qualifying it with the name of the interface it is a +member of since it is recognized as a member of the type itself as well. + +### Compound member access using qualified names + +Compound member access has the form `object.(expression)`, where `expression` is +resolved in the containing scope. This expression may be the _qualified member +name_ of an interface member, that consists of the name of the interface, +possibly qualified with a package or namespace name, a dot `.` and the name of +the member. + +For example, if the `Comparable` interface has a `Less` member method, then the +qualified name of that member is `Comparable.Less`. So if `String` implements +`Comparable`, and `s1` and `s2` are variables of type `String`, then the `Less` method may be called using the qualified member name by writing `s1.(Comparable.Less)(s2)`. -If the interface is implemented internally, then the method can be called using -the unqualified syntax as well. If `String` implements `Printable` internally, -then `s1.Print()` calls the `Print` method of `Printable` as an unqualified -member. +This form may be used to access any member of an interface implemented for a +type, whether it is implemented [internally](#internal-impl) or +[externally](#external-impl). ## Compatible types