From 11cb6eaa2c835a9192408deff29d19b2cbbeb1f1 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 10:41:55 -0700 Subject: [PATCH 01/33] Filling out template with PR 722 --- proposals/README.md | 1 + proposals/p0722.md | 52 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 proposals/p0722.md diff --git a/proposals/README.md b/proposals/README.md index a76c392543d22..b061ff44bd104 100644 --- a/proposals/README.md +++ b/proposals/README.md @@ -66,5 +66,6 @@ request: - [0646 - Low context-sensitivity principle](p0646.md) - [0676 - `:!` generic syntax](p0676.md) - [0680 - And, or, not](p0680.md) +- [0722 - Nominal classes and methods](p0722.md) diff --git a/proposals/p0722.md b/proposals/p0722.md new file mode 100644 index 0000000000000..53347a0d264d7 --- /dev/null +++ b/proposals/p0722.md @@ -0,0 +1,52 @@ +# Nominal classes and methods + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/722) + + + +## 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. + +## Alternatives considered + +TODO: What alternative solutions have you considered? From 5b7edb1ae675ab24576ebef052cedff44e0b164a Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 10:47:45 -0700 Subject: [PATCH 02/33] Fill in proposal --- proposals/p0722.md | 226 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 210 insertions(+), 16 deletions(-) diff --git a/proposals/p0722.md b/proposals/p0722.md index 53347a0d264d7..75f6bf76d68d4 100644 --- a/proposals/p0722.md +++ b/proposals/p0722.md @@ -15,38 +15,232 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Problem](#problem) - [Background](#background) - [Proposal](#proposal) -- [Details](#details) - [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) - [Alternatives considered](#alternatives-considered) + - [Method syntax](#method-syntax) + - [Rationale](#rationale) + - [Full receiver type](#full-receiver-type) + - [Method names line up](#method-names-line-up) + - [Receiver in square brackets](#receiver-in-square-brackets) + - [Receiver parameter is named `me`](#receiver-parameter-is-named-me) + - [Marking mutating methods at the call site](#marking-mutating-methods-at-the-call-site) + - [Keyword to indicate pass by address](#keyword-to-indicate-pass-by-address) + - [Differences between functions and methods](#differences-between-functions-and-methods) ## Problem -TODO: What problem are you trying to solve? How important is that problem? Who -is impacted by it? +We need facilities for defining new nominal +[record types]() in +Carbon. They will need to support object-oriented features such as methods. ## Background -TODO: Is there any background that readers should consider to fully understand -this problem and your approach to solving it? +This is a follow up to +[#561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561) +which laid a foundation. ## 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. +The proposal is to update [docs/design/classes.md](docs/design/classes.md) as +described in [this PR](https://github.com/carbon-language/carbon-lang/pull/701). ## 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. +This particular proposal is focusing on [the Carbon +goal](/docs/project/goals.md#code-that-is-easy-to-read-understand-and -write) +that code is "easy to read, understand, and write." Future proposals will +address other aspects of the class type design such as performance. ## Alternatives considered -TODO: What alternative solutions have you considered? +### Method syntax + +The proposed method syntax was decided in question-for-leads issue +[#494: Method syntax](https://github.com/carbon-language/carbon-lang/issues/494). + +A wide variety of alternatives were considered: + +- Using a different introducer such as `method` to distinguish methods from + functions. +- Putting a pattern to match the receiver before the method name. +- Omitting either the receiver name or the receiver type. +- Allowing the user to specify the receiver name instead of using a parameter + named `me` to indicate it is a method. +- Using syntax between `fn` and the method name to indicate that it is a + method, and whether the method takes a value or the address of the receiver. +- Using a keyword to indicate that the first parameter is the receiver, as in + [C++'s "Deducing `this`" proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r6.html). +- Using a delimiter in the parameter list after the receiver type, but the + main choice of `;` seemed pretty subtle. +- Putting the receiver pattern in a separate set of brackets. This could be an + additional set of parens `(`...`)` before the explicit parameter list. Or + the deduced parameters could be put in angle brackets `<`...`>` and the + receiver in square brackes `[`...`]`. + +Examples: + +``` +method (this: Self*) Set(n: Int); +fn [Me*].Set(n: Int); +fn ->Set(n: Int); +fn& Set(n: Int); +fn &Set(s: Self*, n: Int); +fn Set(this self: Self*, n: Int); +fn Set[this self: Self*](n: Int); +fn Set(self: Self*; n: Int); +fn Set(self: Self*)(n: Int); +``` + +Note: These examples are written using the parameter syntax decided in +[issue #542](https://github.com/carbon-language/carbon-lang/issues/542), though +that does not match the syntax used in the discussion at the time. + +#### Rationale + +##### Full receiver type + +We wanted the option to specify the type of the receiver. In C++, the type of +this is controlled by way of special syntax, such as putting a `const` keyword +at the end of the method declaration. We wanted to treat the receiver type more +consistently with other parameter types, like is being considered in +[C++'s "Deducing `this`" proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r6.html). +That proposal also showed the value of including the entire type, rather than +just part such as whether the parameter would be passed by value or address. We +already plan to use this feature to conditionally include methods based on +whether parameters to the type satisfy additional constraints. In this example, +`FixedArray(T, N)` has a `Print()` method if `T` is `Printable`: + +``` +class FixedArray(T:! Type, N:! Int) { + // ... + fn Print[me: FixedArray(P:! Printable, N)]() { ... } +} +``` + +We did consider the option of making the type of the receiver optional. This is +not what we decided to try first, but is an idea we would consider in the +future. + +##### Method names line up + +Using the same introducer `fn` as functions, as decided in +[issue #463](https://github.com/carbon-language/carbon-lang/issues/463), along +with the method name immediately afterwards means that function and method names +line up in the same column visually. + +``` +struct IntContainer { + // Non-methods for building instances + fn MakeFromInts ... -> IntContainer; + fn MakeRepeating ... -> IntContainer; + + // Methods + fn Size ... -> Int; + fn First ... -> Int; + + fn Clear ...; + fn Append ...; +} +``` + +This is to ease scanning and to put the most important information up front. + +We also considered using another 2-character introducer that would distinguish +methods from associated functions, like `me`, but it did not garner much +support. The name `me` was not evocative enough of "method", and people +generally preferred matching other function declarations. We also considered +using different introducers to communicate whether the receiver was passed by +value or by address, for example `ro` for "read-only" and `rw` for "read-write". + +##### Receiver in square brackets + +Putting the receiver pattern inside the explicit parameter list, as is done in +Python, would lead to an unfortunate mismatch between the arguments listed at +the call and the function declaration. That motivated putting the receiver +pattern inside square brackets (`[`...`]`) with deduced parameters. There were +concerns, however, that this parameter did not act like deduced parameters. + +##### Receiver parameter is named `me` + +Having the receiver name be fixed is helpful for consistency. This benefits +readers, and simplfies copying or moving code between functions. A fixed +receiver name also allows us to identify the receiver pattern in the function +declaration, and identify it as a method instead of an ordinary function +associated with the type, like +[static methods in C++](). + +Finally, we wanted the bodies of methods to access members of the object using +an explicit member access. This means we need the name used to access members to +be short, and so we chose the name `me`. + +##### Marking mutating methods at the call site + +We considered making it visible at the call site when the receiver was passed by +address, with the address-of operator `&`. + +``` +(&x).Set(4); +``` + +This would in effect make the mutating methods be methods on the pointer type +rather than the class type. + +##### Keyword to indicate pass by address + +We considered using just whether the receiver type was a pointer type to +indicate whether the receiver was passed by address, but we preferred type +information to flow in one direction. Otherwise this would cause a problem if we +want to allow _deduction_ of the object parameter type. This deduction can't be +used to select between pointer and not pointer. + +We also considered supporting reference types that could bind to lvalues without +explicitly taking the address of the object, but references would have added a +lot of complexity to the type system and we believed that they are not otherwise +necessary. + +We decided to adopt an approach where the parameter declaration can contain a +marker for matching an argument's +[value category](https://en.cppreference.com/w/cpp/language/value_category) +separately from its type. This marker would be present for methods that mutate +the object, and so requires that the receiver be an lvalue to match the pattern. +The marker means "first take the address of the argument and then match the rest +of the pattern." We considered a few different possible ways to write this: + +``` +fn Set[&me: Self*](n: Int); +fn Set[*(me: Self*)](n: Int); +fn Set[ref me: Self*](n: Int); +``` + +We eventually settled on using a keyword `addr`. + +``` +fn Set[addr me: Self*](n: Int); +``` + +This doesn't use up a symbol, makes it easier to find in search engines, and +allows us to add more keywords in the future for other calling conventions, such +as `inout`. We may even find that those other keywords have preferable semantics +so we may eventually drop `addr`. + +This keyword approach also is consistent with how we are marking template +parameters with the `template` keyword, as decided in +[issue #565 on generic syntax](https://github.com/carbon-language/carbon-lang/issues/565). + +### Differences between functions and methods + +Question-for-leads issue +[#494: Method syntax](https://github.com/carbon-language/carbon-lang/issues/494) +also considered what would be different between functions and methods: + +- Methods use a different calling syntax: `x.F(n)`. +- Methods distinguish between taking the receiver (`x`) by value or by + pointer, without changing the call syntax. +- Methods have to be declared in the body of the class. +- Methods and associated functions both have access to private members of the + class. +- Only methods can opt in to using dynamic dispatch. +- The receiver parameter to a method varies covariantly in inheritance unlike + other parameter types. From 6260fdc8cae3e2dc8b4fb8b5665849e513480d4d Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 10:51:45 -0700 Subject: [PATCH 03/33] Update design/README.md --- docs/design/README.md | 75 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 78b9799aa6d1a..a9e5b19bcda68 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -43,8 +43,11 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Arrays and slices](#arrays-and-slices) - [User-defined types](#user-defined-types) - [Classes](#classes) + - [Assignment, copying](#assignment-copying) + - [Member access](#member-access) + - [Methods](#methods) - [Allocation, construction, and destruction](#allocation-construction-and-destruction) - - [Assignment, copying, and moving](#assignment-copying-and-moving) + - [Moving](#moving) - [Comparison](#comparison) - [Implicit and explicit conversion](#implicit-and-explicit-conversion) - [Inline type composition](#inline-type-composition) @@ -599,8 +602,7 @@ fn RemoveLast(x: (Int, Int, Int)) -> (Int, Int) { > References: [Classes](classes.md) -Classes are a way for users to define their own data strutures or named product -types. +Classes are a way for users to define their own data strutures or record types. For example: @@ -620,11 +622,7 @@ Breaking apart `Widget`: - `Widget` has one `String` member: `payload`. - Given an instance `dial`, a member can be referenced with `dial.paylod`. -##### Allocation, construction, and destruction - -> **TODO:** Needs a feature design and a high level summary provided inline. - -##### Assignment, copying, and moving +##### Assignment, copying You may use a _structural data class literal_, also known as a _struct literal_, to assign or initialize a variable with a class type. @@ -641,6 +639,67 @@ var thingy: Widget = sprocket; sprocket = thingy; ``` +##### Member access + +The data members of a variable with a class type may be accessed using dot `.` +notation: + +```carbon +Assert(sprocket.x == thingy.x); +``` + +##### Methods + +Class type definitions can include methods: + +```carbon +class Point { + fn Distance[me: Self](x2: i32, y2: i32) -> f32 { + var dx: i32 = x2 - me.x; + var dy: i32 = y2 - me.y; + return Math.Sqrt(dx * dx - dy * dy); + } + fn Offset[addr me: Self*](dx: i32, dy: i32); + + var x: i32; + var y: i32; +} + +fn Point.Offset[addr me: Self*](dx: i32, dy: i32) { + me->x += dx; + me->y += dy; +} + +var origin: Point = {.x = 0, .y = 0}; +Assert(Math.Abs(origin.Distance(3, 4) - 5.0) < 0.001); +origin.Offset(3, 4); +Assert(origin.Distance(3, 4) == 0.0); +``` + +This defines a `Point` class type with two integer data members `x` and `y` and +two methods `Distance` and `Offset`: + +- Methods are defined as functions with a `me` parameter inside square + brackets `[`...`]` before the regular explicit parameter list in parens + `(`...`)`. +- Methods are called using using the member syntax, `origin.Distance(`...`)` + and `origin.Offset(`...`)`. +- `Distance` computes and returns the distance to another point, without + modifying the `Point`. This is signified using `[me: Self]` in the method + declaration. +- `origin.Offset(...)` does modify the value of `origin`. This is signified + using `[addr me: Self*]` in the method declaration. +- Methods may be declared lexically inline like `Distance`, or lexically out + of line like `Offset`. + +##### Allocation, construction, and destruction + +> **TODO:** Needs a feature design and a high level summary provided inline. + +##### Moving + +> **TODO:** Needs a feature design and a high level summary provided inline. + ##### Comparison > **TODO:** Needs a feature design and a high level summary provided inline. From 52ecf5f672de27909653e051a3967ba09d7e25c8 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 11:03:03 -0700 Subject: [PATCH 04/33] Add new classes content --- docs/design/classes.md | 308 +++++++++++++++++++++++++++++++---------- 1 file changed, 233 insertions(+), 75 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index ff915aa0a9e47..52696f80515fc 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -30,18 +30,23 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Type expression](#type-expression) - [Assignment and initialization](#assignment-and-initialization) - [Operations performed field-wise](#operations-performed-field-wise) -- [Future work](#future-work) - - [Nominal class types](#nominal-class-types) +- [Nominal class types](#nominal-class-types) + - [Forward declaration](#forward-declaration) + - [Self](#self) - [Construction](#construction) + - [Associated functions](#associated-functions) + - [Methods](#methods) + - [Name lookup in method definitions](#name-lookup-in-method-definitions) + - [Nominal data classes](#nominal-data-classes) - [Member type](#member-type) - - [Self](#self) - [Let](#let) - - [Methods](#methods) + - [Alias](#alias) + - [Access control](#access-control) +- [Future work](#future-work) - [Optional named parameters](#optional-named-parameters) - [Field defaults for struct types](#field-defaults-for-struct-types) - [Destructuring in pattern matching](#destructuring-in-pattern-matching) - [Discussion](#discussion) - - [Access control](#access-control) - [Operator overloading](#operator-overloading) - [Inheritance](#inheritance) - [C++ abstract base classes interoperating with object-safe interfaces](#c-abstract-base-classes-interoperating-with-object-safe-interfaces) @@ -635,68 +640,66 @@ Destruction is performed field-wise in reverse order. Extending user-defined operations on the fields to an operation on an entire data class is [future work](#interfaces-implemented-for-data-classes). -## Future work +## Nominal class types -This includes features that need to be designed, questions to answer, and a -description of the provisional syntax in use until these decisions have been -made. +The declarations for nominal class types will have: -### Nominal class types +- `class` introducer +- the name of the class +- in the future, we will have optional modifiers for inheritance +- `{`, an open curly brace +- a sequence of declarations +- `}`, a close curly brace -The declarations for nominal class types will have a different format. -Provisionally we have been using something like this: +Declarations should generally match declarations that can be declared in other +contexts, for example variable declarations with `var` will define +[instance variables](https://en.wikipedia.org/wiki/Instance_variable): ``` class TextLabel { - var x: Int; - var y: Int; + var x: i32; + var y: i32; - var text: String; + var text: String = "default"; } ``` -It is an open question, though, how we will address the -[different use cases](#use-cases). For example, we might mark -[data classes](#data-classes) with an `impl as Data {}` line. +The main difference here is that `"default"` is a default instead of an +initializer, and will be ignored if another value is supplied for that field +when constructing a value. -### Construction +### Forward declaration -There are a variety of options for constructing class values, we might choose to -support, including initializing from struct values: +To support circular references between class types, we allow +[forward declaration](https://en.wikipedia.org/wiki/Forward_declaration) of +types. A type that is forward declared is considered incomplete until the end of +a definition with the same name. ``` -var p1: Point2D = {.x = 1, .y = 2}; -var p2: auto = {.x = 1, .y = 2} as Point2D; -var p3: auto = Point2D{.x = 1, .y = 2}; -var p4: auto = Point2D(1, 2); -``` - -### Member type +class GraphNode; -Additional types may be defined in the scope of a class definition. +class GraphEdge { + var head: GraphNode*; + var tail: GraphNode*; +} -``` -class StringCounts { - class Node { - var key: String; - var count: Int; - } - var counts: Vector(Node); +class GraphNode { + var edges: Vector(GraphEdge*); } ``` -The inner type is a member of the type, and is given the name -`StringCounts.Node`. +**Open question:** What is specifically allowed and forbidden with an incomplete +type has not yet been decided. ### Self A `class` definition may provisionally include references to its own name in -limited ways, similar to an incomplete type. What is allowed and forbidden is an -open question. +limited ways. These limitations arise from the type not being complete until the +end of its definition is reached. ``` class IntListNode { - var data: Int; + var data: i32; var next: IntListNode*; } ``` @@ -706,7 +709,7 @@ current type, is: ``` class IntListNode { - var data: Int; + var data: i32; var next: Self*; } ``` @@ -716,48 +719,213 @@ class IntListNode { ``` class IntList { class IntListNode { - var data: Int; + var data: i32; var next: Self*; } var first: IntListNode*; } ``` +### Construction + +Any function with access to all the data fields of a class can construct one by +converting a [struct value](#struct-types) to the class type: + +``` +var tl1: TextLabel = {.x = 1, .y = 2}; +var tl2: auto = {.x = 1, .y = 2} as TextLabel; + +Assert(tl1.x == tl2.x); + +fn ReturnsATextLabel() -> TextLabel { + return {.x = 1, .y = 2}; +} +var tl3: TextLabel = ReturnsATextLabel(); + +fn AcceptsATextLabel(tl: TextLabel) -> i32 { + return tl.x + tl.y; +} +Assert(AcceptsATextLabel({.x = 2, .y = 4}) == 6); +``` + +### Associated functions + +An associated function is like a +[C++ static member function or method](), +and is declared like a function at file scope. The declaration can include a +definition of the function body, or that definition can be provided out of line +after the class definition is finished. The most common use is for constructor +functions. + +``` +class Point { + fn Origin() -> Self { + return {.x = 0, .y = 0}; + } + fn CreateCentered() -> Self; + + var x: i32; + var y: i32; +} + +fn Point.CreateCentered() -> Self { + return {.x = ScreenWidth() / 2, .y = ScreenHeight() / 2}; +} +``` + +Associated functions are members of the type, and may be accessed as using dot +`.` member access either the type or any instance. + +``` +var p1: Point = Point.Origin(); +var p2: Point = p1.CreateCentered(); +``` + +### Methods + +[Method]() +declarations are distinguished from other +[function declarations](#associated-functions) by having a `me` parameter in +square brackets `[`...`]` before the explicit parameter list in parens +`(`...`)`. There is no implicit member access in methods, so inside the method +body members are accessed through the `me` parameter. Methods may be written +lexically inline or after the class declaration. + +```carbon +class Circle { + fn Diameter[me: Self]() -> f32 { + return me.radius * 2; + } + fn Expand[addr me: Self*](distance: f32); + + var center: Point; + var radius: f32; +} + +fn Circle.Expand[addr me: Self*](distance: f32) { + me->radius += distance; +} + +var c: Circle = {.center = Point.Origin(), .radius = 1.5 }; +Assert(Math.Abs(c.Diameter() - 3.0) < 0.001); +c.Expand(0.5); +Assert(Math.Abs(c.Diameter() - 4.0) < 0.001); +``` + +- Methods are called using using the dot `.` member syntax, `c.Diameter()` and + `c.Expand(`...`)`. +- `Diameter` computes and returns the diameter of the circle without modifying + the `Circle` instance. This is signified using `[me: Self]` in the method + declaration. +- `c.Expand(...)` does modify the value of `c`. This is signified using + `[addr me: Self*]` in the method declaration. + +FIXME: Meaning of `addr` + +FIXME: Interaction with deduced generic parameters. + +#### Name lookup in method definitions + +FIXME + +### Nominal data classes + +We will mark [data classes](#data-classes) with an `impl as Data {}` line. + +``` +class TextLabel { + var x: i32; + var y: i32; + + var text: String; + + // This line makes `TextLabel` a data class, which defines + // a number of operations field-wise. + impl as Data {} +} +``` + +The fields of data classes must all be public. That line will add +[field-wise implementations and operations of all interfaces that a struct with the same fields would get by default](#operations-performed-field-wise). + +### Member type + +Additional types may be defined in the scope of a class definition. + +``` +class StringCounts { + class Node { + var key: String; + var count: i32; + } + var counts: Vector(Node); +} +``` + +The inner type is a member of the type, and is given the name +`StringCounts.Node`. + ### Let -Other type constants can provisionally be defined using a `let` declaration: +Other type constants can be defined using a `let` declaration: ``` class MyClass { - let Pi: Float32 = 3.141592653589793; - let IndexType: Type = Int; + let Pi: f32 = 3.141592653589793; + let IndexType: Type = i32; } ``` -There are definite questions about this syntax: +These do not affect the storage of instances of that class. -- Should these use the `:!` generic syntax decided in - [issue #565](https://github.com/carbon-language/carbon-lang/issues/565)? -- Would we also have `alias` declarations? They would only be used for names, - not other constant values. +**Open question:** Should these use the `:!` generic syntax decided in +[issue #565](https://github.com/carbon-language/carbon-lang/issues/565)? -### Methods +### Alias -A future proposal will incorporate -[method]() -declaration, definition, and calling into classes. The syntax for declaring -methods has been decided in -[question-for-leads issue #494](https://github.com/carbon-language/carbon-lang/issues/494). -Summarizing that issue: +You may declare aliases of the names of class members. This is to allow them to +be renamed in multiple steps or support alternate names. -- Accessors are written: `fn Diameter[me: Self]() -> Float { ... }` -- Mutators are written: `fn Expand[addr me: Self*](distance: Float) { ... }` -- Associated functions that don't take a receiver at all, like - [C++'s static methods](), - are written: `fn Create() -> Self { ... }` +``` +class StringPair { + var key: String; + var value: String; + alias first = key; + alias second = value; +} + +var sp1: StringPair = {.key = "K", .value = "1"}; +var sp2: StringPair = {.first = "K", .second = "2"}; +Assert(sp1.first == sp2.key); +Assert(&sp1.first == &sp1.key); +``` + +### Access control + +FIXME: + +- `private`: just accessible to members of the class, like C++ +- `internal`: works within the library + tests, does not provide linkage, does + not export symbols through an `import` boundary; if accessed through a + template or an inlined function body, needs to be `private` instead + (probably based on the compiler telling you that you need to) +- `friend`: works just within a package, does not introduce a new name + +LATER: `protected`, maybe `package` -We do not expect to have implicit member access in methods, so inside the method -body members will be accessed through the `me` parameter. +We will need some way of controlling access to the members of classes. By +default, all members are fully publicly accessible, as decided in +[issue #665](https://github.com/carbon-language/carbon-lang/issues/665). + +The set of access control options Carbon will support is an open question. Swift +and C++ (especially w/ modules) provide a lot of options and a pretty wide space +to explore here. + +## Future work + +This includes features that need to be designed, questions to answer, and a +description of the provisional syntax in use until these decisions have been +made. ### Optional named parameters @@ -837,16 +1005,6 @@ Some discussion on this topic has occurred in: [2](https://docs.google.com/document/d/1u6GORSkcgThMAiYKOqsgALcEviEtcghGb5TTVT-U-N0/edit) - ["match" in syntax choices doc](https://docs.google.com/document/d/1iuytei37LPg_tEd6xe-O6P_bpN7TIbEjNtFMLYW2Nno/edit#heading=h.y566d16ivoy2) -### Access control - -We will need some way of controlling access to the members of classes. By -default, all members are fully publicly accessible, as decided in -[issue #665](https://github.com/carbon-language/carbon-lang/issues/665). - -The set of access control options Carbon will support is an open question. Swift -and C++ (especially w/ modules) provide a lot of options and a pretty wide space -to explore here. - ### Operator overloading This includes destructors, copy and move operations, as well as other Carbon From 684b74a93ee7eda4b4324f0287604b793a5a3e72 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 13:06:22 -0700 Subject: [PATCH 05/33] Methods and name lookup --- docs/design/classes.md | 58 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 52696f80515fc..70a9c09c4ce34 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -820,13 +820,65 @@ Assert(Math.Abs(c.Diameter() - 4.0) < 0.001); - `c.Expand(...)` does modify the value of `c`. This is signified using `[addr me: Self*]` in the method declaration. -FIXME: Meaning of `addr` +The pattern '`addr` _patt_' means "first take the address of the argument, which +must be an +[l-value](), and +then match _patt_ against it". -FIXME: Interaction with deduced generic parameters. +If the method declaration also includes +[deduced generic parameters](/docs/design/generics/overview.md#deduced-parameters), +the `me` parameter must be in the same list in square brackets `[`...`]`. The +`me` parameter may appear in any position in that list, as long as it appears +after any names needed to describe its type. #### Name lookup in method definitions -FIXME +When defining a method lexically inline, we need to delay type checking until +the definition of the current type is complete. This means that member lookup is +also delayed. That means that you can reference `me.F()` in a lexically inline +method definition even before the declaration of `F` in that class definition. +However, unqualified names still need to be declared before. + +```carbon +class Point { + fn Distance[me: Self]() -> f32 { + // Allowed: look up of `x` and `y` delayed until + // `type_of(me) == Self` is complete. + return Math.Sqrt(me.x * me.x + me.y * me.y); + } + + fn CreatePolar(r: f32, theta: f32) -> Point { + // Forbidden: unqualified name used before declaration. + return Create(r * Math.Cos(theta), r * Math.Sin(theta)); + // Allowed: look up of `Create` delayed until `Point` is complete. + return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + // Allowed: look up of `Create` delayed until `Self` is complete. + return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + } + + fn Create(x: f32, y: f32) -> Point { + // Allowed: checking that conversion of `{.x: f32, .y: f32}` + // to `Point` is delayed until `Point` is complete. + return {.x = x, .y = y}; + } + + fn CreateXEqualsY(xy: f32) -> Point { + // Allowed: `Create` is declared earlier. + return Create(xy, xy); + } + + fn Angle[me: Self]() -> f32; + + var x: f32; + var y: f32; +} + +fn Point.Angle[me: Self]() -> f32 { + // Allowed: `Point` type is complete, + // function is checked immediately. + return Math.ATan2(me.y, me.x); +} +``` ### Nominal data classes From 5eb0e58146ab5544fe1ee84b9cd2ce3e8e8a7b65 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 14:16:51 -0700 Subject: [PATCH 06/33] Access control --- .codespell_ignore | 1 + docs/design/classes.md | 101 ++++++++++++++++++++++++++++++++--------- 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/.codespell_ignore b/.codespell_ignore index ab2f6f29a94cd..9e2891769400c 100644 --- a/.codespell_ignore +++ b/.codespell_ignore @@ -4,6 +4,7 @@ circularly copyable +crate inout pullrequest statics diff --git a/docs/design/classes.md b/docs/design/classes.md index 70a9c09c4ce34..07b2407619eda 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -80,8 +80,9 @@ class variables. The use cases for classes include both cases motivated by C++ interop, and cases that we expect to be included in idiomatic Carbon-only code. -**This design currently only attempts to address the "data classes" use case.** -Addressing the other use cases is future work. +**This design currently only attempts to address the "data classes" and +"encapsulated types without inheritance" use cases.** Addressing the other use +cases is future work. ### Data classes @@ -135,8 +136,7 @@ We expect two kinds of methods on these types: public methods defining the API for accessing and manipulating values of the type, and private helper methods used as an implementation detail of the public methods. -These types are expected in idiomatic Carbon-only code. Extending this design to -support these types is future work. +These types are expected in idiomatic Carbon-only code. #### With inheritance and subtyping @@ -514,8 +514,7 @@ but we expect later to support more ways to define data class types. Also note that there is no `struct` keyword, "struct" is just convenient shorthand terminology for a structural data class. -**Future work:** We intend to support nominal [data classes](#data-classes) as -well. +[Nominal data classes](#nominal-data-classes) are also supported by Carbon. ### Literals @@ -880,6 +879,9 @@ fn Point.Angle[me: Self]() -> f32 { } ``` +**Note:** The details of name lookup are still being decided in issue +[#472: Open question: Calling functions defined later in the same file](https://github.com/carbon-language/carbon-lang/issues/472). + ### Nominal data classes We will mark [data classes](#data-classes) with an `impl as Data {}` line. @@ -954,24 +956,79 @@ Assert(&sp1.first == &sp1.key); ### Access control -FIXME: - -- `private`: just accessible to members of the class, like C++ -- `internal`: works within the library + tests, does not provide linkage, does - not export symbols through an `import` boundary; if accessed through a - template or an inlined function body, needs to be `private` instead - (probably based on the compiler telling you that you need to) -- `friend`: works just within a package, does not introduce a new name +By default, all members of a class are fully publicly accessible. Access can be +restricted by adding a keyword, called an +[access modifier](https://en.wikipedia.org/wiki/Access_modifiers), prior to the +declaration. -LATER: `protected`, maybe `package` - -We will need some way of controlling access to the members of classes. By -default, all members are fully publicly accessible, as decided in -[issue #665](https://github.com/carbon-language/carbon-lang/issues/665). +```carbon +class Point { + fn Distance[me: Self]() -> f32; + // These are only accessible to members of `Point`. + private var x: f32; + private var y: f32; +} +``` -The set of access control options Carbon will support is an open question. Swift -and C++ (especially w/ modules) provide a lot of options and a pretty wide space -to explore here. +As in C++, `private` means only accessible to members of the class. Carbon has +two other access modifiers that restrict access to members of the class and also +specify the [linkage](): + +- `private.internal` gives the member internal linkage, improving build + scalability. +- `private.external` gives the member external linkage, allowing it to be used + in inline methods and templates. + +Normally `private` will give the member internal linkage unless it needs to be +external because it is used in an inline method or template. + +**Future work:** We will add support for `protected` access when inheritance is +added to this design. We will also define a convenient way for tests that belong +to the same library to get access to private members. + +**Open questions:** Using `private` to mean "restricted to this class" matches +C++. Other languages support restricting to different scopes: + +- Swift supports "restrict to this module" and "restrict to this file". +- Rust supports "restrict to this module and any children of this module", as + well as "restrict to this crate", "restrict to parent module", and "restrict + to a specific ancestor module". + +**Comparison to other languages:** C++, Rust, and Swift all make class members +private by default. C++ offers the `struct` keyword that makes members public by +default. + +**Rationale:** Carbon makes members public by default for a few reasons: + +- The readability of public members is the most important, since we expect + most readers to be concerned with the public API of a type. +- The members that are most commonly private are the data fields, which have + relatively less complicated definitions that suffer less from the extra + annotation. + +Additionally, there is precedent for this approach in modern object-oriented +languages such as +[Kotlin](https://kotlinlang.org/docs/visibility-modifiers.html) and +[Python](https://docs.python.org/3/tutorial/classes.html), both of which are +well regarded for their usability. + +Keywords controlling visibility are attached to individual declarations instead +of C++'s approach of labels controlling the visibility for all following +declarations to +[reduce context sensitivity](/docs/project/principles/low_context_sensitivity.md). +This matches +[Rust](https://doc.rust-lang.org/reference/visibility-and-privacy.html), +[Swift](https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html), +[Java](http://rosettacode.org/wiki/Classes#Java), +[C#](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers), +and [D](https://wiki.dlang.org/Access_specifiers_and_visibility). + +**References:** Proposal +[#561: Basic classes](https://github.com/carbon-language/carbon-lang/pull/561) +included the decision that +[members default to publicly accessible](/proposals/p0561.md#access-control) +originally asked in issue +[#665](https://github.com/carbon-language/carbon-lang/issues/665). ## Future work From 87530b1785f052884ac38501e5717db645e5d586 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 14:47:34 -0700 Subject: [PATCH 07/33] Checkpoint progress. --- docs/design/classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 07b2407619eda..1b1739044956f 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -979,8 +979,8 @@ specify the [linkage](): - `private.external` gives the member external linkage, allowing it to be used in inline methods and templates. -Normally `private` will give the member internal linkage unless it needs to be -external because it is used in an inline method or template. +`private` will give the member internal linkage unless it needs to be external +because it is used in an inline method or template. **Future work:** We will add support for `protected` access when inheritance is added to this design. We will also define a convenient way for tests that belong From 41a7cf002b7469e1d57196e88ed0e2357741a72e Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 16:39:58 -0700 Subject: [PATCH 08/33] Checkpoint progress. --- docs/design/classes.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 1b1739044956f..2ea1d17e4dfa0 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -665,7 +665,8 @@ class TextLabel { The main difference here is that `"default"` is a default instead of an initializer, and will be ignored if another value is supplied for that field -when constructing a value. +when constructing a value. Defaults must be constants whose value can be +determined at compile time. ### Forward declaration @@ -747,6 +748,10 @@ fn AcceptsATextLabel(tl: TextLabel) -> i32 { Assert(AcceptsATextLabel({.x = 2, .y = 4}) == 6); ``` +Note that a nominal class, unlike a [struct type](#type-expression), can define +default values for fields, and so may be initialized with a +[struct value](#literals) that omits some or all of those fields. + ### Associated functions An associated function is like a @@ -822,7 +827,7 @@ Assert(Math.Abs(c.Diameter() - 4.0) < 0.001); The pattern '`addr` _patt_' means "first take the address of the argument, which must be an [l-value](), and -then match _patt_ against it". +then match pattern _patt_ against it". If the method declaration also includes [deduced generic parameters](/docs/design/generics/overview.md#deduced-parameters), From 0ea7cda147d38b8c0b3bab62f57c45043cac90ef Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 16:41:56 -0700 Subject: [PATCH 09/33] Try using diff code blocks to show allowed/forbidden --- docs/design/classes.md | 78 +++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 2ea1d17e4dfa0..27fdef756e1fd 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -843,45 +843,45 @@ also delayed. That means that you can reference `me.F()` in a lexically inline method definition even before the declaration of `F` in that class definition. However, unqualified names still need to be declared before. -```carbon -class Point { - fn Distance[me: Self]() -> f32 { - // Allowed: look up of `x` and `y` delayed until - // `type_of(me) == Self` is complete. - return Math.Sqrt(me.x * me.x + me.y * me.y); - } - - fn CreatePolar(r: f32, theta: f32) -> Point { - // Forbidden: unqualified name used before declaration. - return Create(r * Math.Cos(theta), r * Math.Sin(theta)); - // Allowed: look up of `Create` delayed until `Point` is complete. - return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); - // Allowed: look up of `Create` delayed until `Self` is complete. - return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); - } - - fn Create(x: f32, y: f32) -> Point { - // Allowed: checking that conversion of `{.x: f32, .y: f32}` - // to `Point` is delayed until `Point` is complete. - return {.x = x, .y = y}; - } - - fn CreateXEqualsY(xy: f32) -> Point { - // Allowed: `Create` is declared earlier. - return Create(xy, xy); - } - - fn Angle[me: Self]() -> f32; - - var x: f32; - var y: f32; -} - -fn Point.Angle[me: Self]() -> f32 { - // Allowed: `Point` type is complete, - // function is checked immediately. - return Math.ATan2(me.y, me.x); -} +```diff + class Point { + fn Distance[me: Self]() -> f32 { + // Allowed: look up of `x` and `y` delayed until + // `type_of(me) == Self` is complete. ++ return Math.Sqrt(me.x * me.x + me.y * me.y); + } + + fn CreatePolar(r: f32, theta: f32) -> Point { + // Forbidden: unqualified name used before declaration. +- return Create(r * Math.Cos(theta), r * Math.Sin(theta)); + // Allowed: look up of `Create` delayed until `Point` is complete. ++ return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + // Allowed: look up of `Create` delayed until `Self` is complete. ++ return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + } + + fn Create(x: f32, y: f32) -> Point { + // Allowed: checking that conversion of `{.x: f32, .y: f32}` + // to `Point` is delayed until `Point` is complete. ++ return {.x = x, .y = y}; + } + + fn CreateXEqualsY(xy: f32) -> Point { + // Allowed: `Create` is declared earlier. ++ return Create(xy, xy); + } + + fn Angle[me: Self]() -> f32; + + var x: f32; + var y: f32; + } + + fn Point.Angle[me: Self]() -> f32 { + // Allowed: `Point` type is complete, + // function is checked immediately. ++ return Math.ATan2(me.y, me.x); + } ``` **Note:** The details of name lookup are still being decided in issue From 2b252d41824e7bf27d227b81e15c53ce88eedb38 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 10 Aug 2021 20:48:49 -0700 Subject: [PATCH 10/33] Revert diff experiment --- docs/design/classes.md | 76 +++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 27fdef756e1fd..2a20502a1cc94 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -844,44 +844,44 @@ method definition even before the declaration of `F` in that class definition. However, unqualified names still need to be declared before. ```diff - class Point { - fn Distance[me: Self]() -> f32 { - // Allowed: look up of `x` and `y` delayed until - // `type_of(me) == Self` is complete. -+ return Math.Sqrt(me.x * me.x + me.y * me.y); - } - - fn CreatePolar(r: f32, theta: f32) -> Point { - // Forbidden: unqualified name used before declaration. -- return Create(r * Math.Cos(theta), r * Math.Sin(theta)); - // Allowed: look up of `Create` delayed until `Point` is complete. -+ return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); - // Allowed: look up of `Create` delayed until `Self` is complete. -+ return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); - } - - fn Create(x: f32, y: f32) -> Point { - // Allowed: checking that conversion of `{.x: f32, .y: f32}` - // to `Point` is delayed until `Point` is complete. -+ return {.x = x, .y = y}; - } - - fn CreateXEqualsY(xy: f32) -> Point { - // Allowed: `Create` is declared earlier. -+ return Create(xy, xy); - } - - fn Angle[me: Self]() -> f32; - - var x: f32; - var y: f32; - } - - fn Point.Angle[me: Self]() -> f32 { - // Allowed: `Point` type is complete, - // function is checked immediately. -+ return Math.ATan2(me.y, me.x); - } +class Point { + fn Distance[me: Self]() -> f32 { + // Allowed: look up of `x` and `y` delayed until + // `type_of(me) == Self` is complete. + return Math.Sqrt(me.x * me.x + me.y * me.y); + } + + fn CreatePolar(r: f32, theta: f32) -> Point { + // Forbidden: unqualified name used before declaration. + return Create(r * Math.Cos(theta), r * Math.Sin(theta)); + // Allowed: look up of `Create` delayed until `Point` is complete. + return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + // Allowed: look up of `Create` delayed until `Self` is complete. + return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + } + + fn Create(x: f32, y: f32) -> Point { + // Allowed: checking that conversion of `{.x: f32, .y: f32}` + // to `Point` is delayed until `Point` is complete. + return {.x = x, .y = y}; + } + + fn CreateXEqualsY(xy: f32) -> Point { + // Allowed: `Create` is declared earlier. + return Create(xy, xy); + } + + fn Angle[me: Self]() -> f32; + + var x: f32; + var y: f32; +} + +fn Point.Angle[me: Self]() -> f32 { + // Allowed: `Point` type is complete, + // function is checked immediately. + return Math.ATan2(me.y, me.x); +} ``` **Note:** The details of name lookup are still being decided in issue From 196c13ff38cd634bffc603593051407a382a213b Mon Sep 17 00:00:00 2001 From: josh11b Date: Tue, 10 Aug 2021 21:26:01 -0700 Subject: [PATCH 11/33] Apply suggestions from code review Co-authored-by: Chandler Carruth --- docs/design/README.md | 2 +- docs/design/classes.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index a9e5b19bcda68..27b85695cd474 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -687,7 +687,7 @@ two methods `Distance` and `Offset`: - `Distance` computes and returns the distance to another point, without modifying the `Point`. This is signified using `[me: Self]` in the method declaration. -- `origin.Offset(...)` does modify the value of `origin`. This is signified +- `origin.Offset(`...`)` does modify the value of `origin`. This is signified using `[addr me: Self*]` in the method declaration. - Methods may be declared lexically inline like `Distance`, or lexically out of line like `Offset`. diff --git a/docs/design/classes.md b/docs/design/classes.md index 2a20502a1cc94..225595b1ae8cd 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -758,7 +758,7 @@ An associated function is like a [C++ static member function or method](), and is declared like a function at file scope. The declaration can include a definition of the function body, or that definition can be provided out of line -after the class definition is finished. The most common use is for constructor +after the class definition is finished. A common use is for constructor functions. ``` @@ -821,7 +821,7 @@ Assert(Math.Abs(c.Diameter() - 4.0) < 0.001); - `Diameter` computes and returns the diameter of the circle without modifying the `Circle` instance. This is signified using `[me: Self]` in the method declaration. -- `c.Expand(...)` does modify the value of `c`. This is signified using +- `c.Expand(`...`)` does modify the value of `c`. This is signified using `[addr me: Self*]` in the method declaration. The pattern '`addr` _patt_' means "first take the address of the argument, which From a933ca154c39f99306166dc865704971c0ec10ba Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 08:54:29 -0700 Subject: [PATCH 12/33] Add assignment, doesn't use field defaults --- docs/design/classes.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/design/classes.md b/docs/design/classes.md index 225595b1ae8cd..ab850d82dd4ef 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -34,6 +34,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Forward declaration](#forward-declaration) - [Self](#self) - [Construction](#construction) + - [Assignment](#assignment) - [Associated functions](#associated-functions) - [Methods](#methods) - [Name lookup in method definitions](#name-lookup-in-method-definitions) @@ -752,6 +753,32 @@ Note that a nominal class, unlike a [struct type](#type-expression), can define default values for fields, and so may be initialized with a [struct value](#literals) that omits some or all of those fields. +#### Assignment + +Field defaults are only used when initializing a new value, not assigning to an +existing variable. When assigning, values for all the fields must be specified. +This avoids ambiguity about whether to use the default value or the previous +value for a field. + +``` +var tl: TextLabel = {.x = 1, .y = 2}; +Assert(tl.text == "default"); + +// Allowed: assigns all fields +tl = {.x = 3, .y = 4, .text = "new"}; + +// Forbidden: should tl.text == "default" or "new"? +tl = {.x = 5, .y = 6}; + +// Allowed: This statement is evaluated in two steps: +// 1. {.x = 5, .y = 6} is converted into a new TextLabel value, +// using default for field `text`. +// 2. tl is assigned to a TextLabel, which has values for all +// fields. +tl = {.x = 5, .y = 6} as TextLabel; +Assert(tl.text == "default"); +``` + ### Associated functions An associated function is like a From 380f25d9efaf04628d488604d2580e504d2048b6 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 09:31:46 -0700 Subject: [PATCH 13/33] Emoji red x -> forbidden code --- docs/design/classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index ab850d82dd4ef..d5fbe2bf3bd80 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -768,7 +768,7 @@ Assert(tl.text == "default"); tl = {.x = 3, .y = 4, .text = "new"}; // Forbidden: should tl.text == "default" or "new"? -tl = {.x = 5, .y = 6}; +❌ tl = {.x = 5, .y = 6}; // Allowed: This statement is evaluated in two steps: // 1. {.x = 5, .y = 6} is converted into a new TextLabel value, @@ -880,7 +880,7 @@ class Point { fn CreatePolar(r: f32, theta: f32) -> Point { // Forbidden: unqualified name used before declaration. - return Create(r * Math.Cos(theta), r * Math.Sin(theta)); + ❌ return Create(r * Math.Cos(theta), r * Math.Sin(theta)); // Allowed: look up of `Create` delayed until `Point` is complete. return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); // Allowed: look up of `Create` delayed until `Self` is complete. From 48a042c308097deadb9a0e42292c18bb08d0b4a8 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 09:35:14 -0700 Subject: [PATCH 14/33] Emoji check -> allowed code --- docs/design/classes.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index d5fbe2bf3bd80..92863c36e2cff 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -765,7 +765,7 @@ var tl: TextLabel = {.x = 1, .y = 2}; Assert(tl.text == "default"); // Allowed: assigns all fields -tl = {.x = 3, .y = 4, .text = "new"}; +✅ tl = {.x = 3, .y = 4, .text = "new"}; // Forbidden: should tl.text == "default" or "new"? ❌ tl = {.x = 5, .y = 6}; @@ -775,7 +775,7 @@ tl = {.x = 3, .y = 4, .text = "new"}; // using default for field `text`. // 2. tl is assigned to a TextLabel, which has values for all // fields. -tl = {.x = 5, .y = 6} as TextLabel; +✅ tl = {.x = 5, .y = 6} as TextLabel; Assert(tl.text == "default"); ``` @@ -875,27 +875,27 @@ class Point { fn Distance[me: Self]() -> f32 { // Allowed: look up of `x` and `y` delayed until // `type_of(me) == Self` is complete. - return Math.Sqrt(me.x * me.x + me.y * me.y); + ✅ return Math.Sqrt(me.x * me.x + me.y * me.y); } fn CreatePolar(r: f32, theta: f32) -> Point { // Forbidden: unqualified name used before declaration. ❌ return Create(r * Math.Cos(theta), r * Math.Sin(theta)); // Allowed: look up of `Create` delayed until `Point` is complete. - return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + ✅ return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); // Allowed: look up of `Create` delayed until `Self` is complete. - return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + ✅ return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); } fn Create(x: f32, y: f32) -> Point { // Allowed: checking that conversion of `{.x: f32, .y: f32}` // to `Point` is delayed until `Point` is complete. - return {.x = x, .y = y}; + ✅ return {.x = x, .y = y}; } fn CreateXEqualsY(xy: f32) -> Point { // Allowed: `Create` is declared earlier. - return Create(xy, xy); + ✅ return Create(xy, xy); } fn Angle[me: Self]() -> f32; @@ -907,7 +907,7 @@ class Point { fn Point.Angle[me: Self]() -> f32 { // Allowed: `Point` type is complete, // function is checked immediately. - return Math.ATan2(me.y, me.x); + ✅ return Math.ATan2(me.y, me.x); } ``` From dbe73170863c88da24de036ce375137e1b49100e Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 09:37:45 -0700 Subject: [PATCH 15/33] Split example so one return/method --- docs/design/classes.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 92863c36e2cff..fb05be6013a90 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -878,11 +878,15 @@ class Point { ✅ return Math.Sqrt(me.x * me.x + me.y * me.y); } - fn CreatePolar(r: f32, theta: f32) -> Point { + fn CreatePolarInvalid(r: f32, theta: f32) -> Point { // Forbidden: unqualified name used before declaration. ❌ return Create(r * Math.Cos(theta), r * Math.Sin(theta)); + } + fn CreatePolarValid1(r: f32, theta: f32) -> Point { // Allowed: look up of `Create` delayed until `Point` is complete. ✅ return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + } + fn CreatePolarValid2(r: f32, theta: f32) -> Point { // Allowed: look up of `Create` delayed until `Self` is complete. ✅ return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); } From 0b1d1a0c62a34ac6dec3f5d6e236e7d71ac1a2ae Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 09:39:56 -0700 Subject: [PATCH 16/33] Move emoji to comments --- docs/design/classes.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index fb05be6013a90..cf671b4ec8a51 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -764,18 +764,18 @@ value for a field. var tl: TextLabel = {.x = 1, .y = 2}; Assert(tl.text == "default"); -// Allowed: assigns all fields -✅ tl = {.x = 3, .y = 4, .text = "new"}; +// ✅ Allowed: assigns all fields +tl = {.x = 3, .y = 4, .text = "new"}; -// Forbidden: should tl.text == "default" or "new"? -❌ tl = {.x = 5, .y = 6}; +// ❌ Forbidden: should tl.text == "default" or "new"? +tl = {.x = 5, .y = 6}; -// Allowed: This statement is evaluated in two steps: +// ✅ Allowed: This statement is evaluated in two steps: // 1. {.x = 5, .y = 6} is converted into a new TextLabel value, // using default for field `text`. // 2. tl is assigned to a TextLabel, which has values for all // fields. -✅ tl = {.x = 5, .y = 6} as TextLabel; +tl = {.x = 5, .y = 6} as TextLabel; Assert(tl.text == "default"); ``` @@ -873,33 +873,33 @@ However, unqualified names still need to be declared before. ```diff class Point { fn Distance[me: Self]() -> f32 { - // Allowed: look up of `x` and `y` delayed until + // ✅ Allowed: look up of `x` and `y` delayed until // `type_of(me) == Self` is complete. - ✅ return Math.Sqrt(me.x * me.x + me.y * me.y); + return Math.Sqrt(me.x * me.x + me.y * me.y); } fn CreatePolarInvalid(r: f32, theta: f32) -> Point { - // Forbidden: unqualified name used before declaration. - ❌ return Create(r * Math.Cos(theta), r * Math.Sin(theta)); + // ❌ Forbidden: unqualified name used before declaration. + return Create(r * Math.Cos(theta), r * Math.Sin(theta)); } fn CreatePolarValid1(r: f32, theta: f32) -> Point { - // Allowed: look up of `Create` delayed until `Point` is complete. - ✅ return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + // ✅ Allowed: look up of `Create` delayed until `Point` is complete. + return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); } fn CreatePolarValid2(r: f32, theta: f32) -> Point { - // Allowed: look up of `Create` delayed until `Self` is complete. - ✅ return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); + // ✅ Allowed: look up of `Create` delayed until `Self` is complete. + return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); } fn Create(x: f32, y: f32) -> Point { - // Allowed: checking that conversion of `{.x: f32, .y: f32}` + // ✅ Allowed: checking that conversion of `{.x: f32, .y: f32}` // to `Point` is delayed until `Point` is complete. - ✅ return {.x = x, .y = y}; + return {.x = x, .y = y}; } fn CreateXEqualsY(xy: f32) -> Point { - // Allowed: `Create` is declared earlier. - ✅ return Create(xy, xy); + // ✅ Allowed: `Create` is declared earlier. + return Create(xy, xy); } fn Angle[me: Self]() -> f32; @@ -909,9 +909,9 @@ class Point { } fn Point.Angle[me: Self]() -> f32 { - // Allowed: `Point` type is complete, + // ✅ Allowed: `Point` type is complete, // function is checked immediately. - ✅ return Math.ATan2(me.y, me.x); + return Math.ATan2(me.y, me.x); } ``` From e47930cf3ae80445a63af71c6843cc010a766454 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 12:55:55 -0700 Subject: [PATCH 17/33] Move method calling alternative --- docs/design/classes.md | 5 +++++ proposals/p0722.md | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index cf671b4ec8a51..510b45a471e74 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -44,6 +44,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Alias](#alias) - [Access control](#access-control) - [Future work](#future-work) + - [Struct literal shortcut](#struct-literal-shortcut) - [Optional named parameters](#optional-named-parameters) - [Field defaults for struct types](#field-defaults-for-struct-types) - [Destructuring in pattern matching](#destructuring-in-pattern-matching) @@ -1072,6 +1073,10 @@ This includes features that need to be designed, questions to answer, and a description of the provisional syntax in use until these decisions have been made. +### Struct literal shortcut + +We could allow you to write `{x, y}` as a short hand for `{.x = x, .y = y}`. + ### Optional named parameters Structs are being considered as a possible mechanism for implementing optional diff --git a/proposals/p0722.md b/proposals/p0722.md index 75f6bf76d68d4..7131ca96eb44d 100644 --- a/proposals/p0722.md +++ b/proposals/p0722.md @@ -23,8 +23,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Method names line up](#method-names-line-up) - [Receiver in square brackets](#receiver-in-square-brackets) - [Receiver parameter is named `me`](#receiver-parameter-is-named-me) - - [Marking mutating methods at the call site](#marking-mutating-methods-at-the-call-site) - [Keyword to indicate pass by address](#keyword-to-indicate-pass-by-address) + - [Marking mutating methods at the call site](#marking-mutating-methods-at-the-call-site) - [Differences between functions and methods](#differences-between-functions-and-methods) @@ -175,18 +175,6 @@ Finally, we wanted the bodies of methods to access members of the object using an explicit member access. This means we need the name used to access members to be short, and so we chose the name `me`. -##### Marking mutating methods at the call site - -We considered making it visible at the call site when the receiver was passed by -address, with the address-of operator `&`. - -``` -(&x).Set(4); -``` - -This would in effect make the mutating methods be methods on the pointer type -rather than the class type. - ##### Keyword to indicate pass by address We considered using just whether the receiver type was a pointer type to @@ -229,6 +217,18 @@ This keyword approach also is consistent with how we are marking template parameters with the `template` keyword, as decided in [issue #565 on generic syntax](https://github.com/carbon-language/carbon-lang/issues/565). +### Marking mutating methods at the call site + +We considered making it visible at the call site when the receiver was passed by +address, with the address-of operator `&`. + +``` +(&x).Set(4); +``` + +This would in effect make the mutating methods be methods on the pointer type +rather than the class type. + ### Differences between functions and methods Question-for-leads issue From 17208386b557d88a7675cd52e86dd3b1702d0450 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 12:57:01 -0700 Subject: [PATCH 18/33] Add Kotlin as example --- docs/design/classes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/design/classes.md b/docs/design/classes.md index 510b45a471e74..b6085aa52bcc4 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -1058,6 +1058,7 @@ This matches [Swift](https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html), [Java](http://rosettacode.org/wiki/Classes#Java), [C#](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers), +[Kotlin](https://kotlinlang.org/docs/visibility-modifiers.html#classes-and-interfaces), and [D](https://wiki.dlang.org/Access_specifiers_and_visibility). **References:** Proposal From 11878b809d9a3483f6c77b4fc0413f06a23a1ae7 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 13:12:11 -0700 Subject: [PATCH 19/33] No explicit control over linkage --- docs/design/classes.md | 21 ++++++++------------- proposals/p0722.md | 13 +++++++++++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index b6085aa52bcc4..15cd5dc0e14de 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -42,7 +42,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Member type](#member-type) - [Let](#let) - [Alias](#alias) - - [Access control](#access-control) + - [Private access](#private-access) - [Future work](#future-work) - [Struct literal shortcut](#struct-literal-shortcut) - [Optional named parameters](#optional-named-parameters) @@ -991,7 +991,7 @@ Assert(sp1.first == sp2.key); Assert(&sp1.first == &sp1.key); ``` -### Access control +### Private access By default, all members of a class are fully publicly accessible. Access can be restricted by adding a keyword, called an @@ -1007,22 +1007,17 @@ class Point { } ``` -As in C++, `private` means only accessible to members of the class. Carbon has -two other access modifiers that restrict access to members of the class and also -specify the [linkage](): - -- `private.internal` gives the member internal linkage, improving build - scalability. -- `private.external` gives the member external linkage, allowing it to be used - in inline methods and templates. - -`private` will give the member internal linkage unless it needs to be external -because it is used in an inline method or template. +As in C++, `private` means only accessible to members of the class. **Future work:** We will add support for `protected` access when inheritance is added to this design. We will also define a convenient way for tests that belong to the same library to get access to private members. +**Future work:** `private` will give the member internal linkage unless it needs +to be external because it is used in an inline method or template. We may in the +future +[add a way to specify internal linkage explicitly](/proposals/p0722.md#specifying-linkage-as-part-of-the-access-modifier). + **Open questions:** Using `private` to mean "restricted to this class" matches C++. Other languages support restricting to different scopes: diff --git a/proposals/p0722.md b/proposals/p0722.md index 7131ca96eb44d..dcf7414b52835 100644 --- a/proposals/p0722.md +++ b/proposals/p0722.md @@ -26,6 +26,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Keyword to indicate pass by address](#keyword-to-indicate-pass-by-address) - [Marking mutating methods at the call site](#marking-mutating-methods-at-the-call-site) - [Differences between functions and methods](#differences-between-functions-and-methods) + - [Specifying linkage as part of the access modifier](#specifying-linkage-as-part-of-the-access-modifier) @@ -244,3 +245,15 @@ also considered what would be different between functions and methods: - Only methods can opt in to using dynamic dispatch. - The receiver parameter to a method varies covariantly in inheritance unlike other parameter types. + +### Specifying linkage as part of the access modifier + +We considered various access modifiers such as `internal` or `private.internal` +that would enforce internal linkage in addition to restricted visibility. We +decided to postpone such considerations for the time being. + +Even if we _do_ need explicit controls here, we did not feel the need to +front-load that complexity right now and with relatively less time or experience +to evaluate the tradeoffs. If and when linkage becomes a visible issue and +important to discuss, we can add it. Until then, we can see how much mileage we +can get out of purely implementation techniques. From 4bcb965e6b36c84eb6d1d0b3014bb45f5976f1a6 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 13:15:38 -0700 Subject: [PATCH 20/33] "member"/"associated" -> "nested" --- docs/design/classes.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 15cd5dc0e14de..108412d5fa002 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -35,11 +35,11 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Self](#self) - [Construction](#construction) - [Assignment](#assignment) - - [Associated functions](#associated-functions) + - [Nested functions](#nested-functions) - [Methods](#methods) - [Name lookup in method definitions](#name-lookup-in-method-definitions) - [Nominal data classes](#nominal-data-classes) - - [Member type](#member-type) + - [Nested type](#nested-type) - [Let](#let) - [Alias](#alias) - [Private access](#private-access) @@ -780,9 +780,9 @@ tl = {.x = 5, .y = 6} as TextLabel; Assert(tl.text == "default"); ``` -### Associated functions +### Nested functions -An associated function is like a +An nested function is like a [C++ static member function or method](), and is declared like a function at file scope. The declaration can include a definition of the function body, or that definition can be provided out of line @@ -805,8 +805,8 @@ fn Point.CreateCentered() -> Self { } ``` -Associated functions are members of the type, and may be accessed as using dot -`.` member access either the type or any instance. +Nested functions are members of the type, and may be accessed as using dot `.` +member access either the type or any instance. ``` var p1: Point = Point.Origin(); @@ -817,7 +817,7 @@ var p2: Point = p1.CreateCentered(); [Method]() declarations are distinguished from other -[function declarations](#associated-functions) by having a `me` parameter in +[nested function declarations](#nested-functions) by having a `me` parameter in square brackets `[`...`]` before the explicit parameter list in parens `(`...`)`. There is no implicit member access in methods, so inside the method body members are accessed through the `me` parameter. Methods may be written @@ -939,7 +939,7 @@ class TextLabel { The fields of data classes must all be public. That line will add [field-wise implementations and operations of all interfaces that a struct with the same fields would get by default](#operations-performed-field-wise). -### Member type +### Nested type Additional types may be defined in the scope of a class definition. From 7ada41ed559431f50c3dc29b03537763bc507703 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 13:33:38 -0700 Subject: [PATCH 21/33] Rationale for `impl as Data {}` --- docs/design/classes.md | 3 +++ proposals/p0722.md | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/docs/design/classes.md b/docs/design/classes.md index 108412d5fa002..db3a3bc06599f 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -939,6 +939,9 @@ class TextLabel { The fields of data classes must all be public. That line will add [field-wise implementations and operations of all interfaces that a struct with the same fields would get by default](#operations-performed-field-wise). +**References:** Rationale for this approach is given in proposal +[#722](/proposals/p0722.md#nominal-data-class). + ### Nested type Additional types may be defined in the scope of a class definition. diff --git a/proposals/p0722.md b/proposals/p0722.md index dcf7414b52835..b4b35381d0627 100644 --- a/proposals/p0722.md +++ b/proposals/p0722.md @@ -27,6 +27,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Marking mutating methods at the call site](#marking-mutating-methods-at-the-call-site) - [Differences between functions and methods](#differences-between-functions-and-methods) - [Specifying linkage as part of the access modifier](#specifying-linkage-as-part-of-the-access-modifier) + - [Nominal data class](#nominal-data-class) @@ -257,3 +258,17 @@ front-load that complexity right now and with relatively less time or experience to evaluate the tradeoffs. If and when linkage becomes a visible issue and important to discuss, we can add it. Until then, we can see how much mileage we can get out of purely implementation techniques. + +### Nominal data class + +We needed some way of opting a nominal class into the fieldwise behavior of a +data class. [In Kotlin](https://kotlinlang.org/docs/data-classes.html), you +precede the `class` declaration with a `data` keyword. Carbon already has a way +of marking types as having specific semantic properties, implementing +interfaces. This leverages the support for interfaces in the language to be able +to express things like "here is a blanket implementation of an interface for all +data classes" in a consistent way. + +We chose the name `Data` rather than `DataClass` for the interface since tuples +implicitly implement the interface and were "product types" rather than +"classes". From b572336766bda2b03809b63c9900d4681de48c47 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 15:22:59 -0700 Subject: [PATCH 22/33] Class function and member function terminology --- docs/design/classes.md | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index db3a3bc06599f..6d3bb2a4091c4 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -35,9 +35,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Self](#self) - [Construction](#construction) - [Assignment](#assignment) - - [Nested functions](#nested-functions) - - [Methods](#methods) - - [Name lookup in method definitions](#name-lookup-in-method-definitions) + - [Member functions](#member-functions) + - [Class functions](#class-functions) + - [Methods](#methods) + - [Name lookup in member function definitions](#name-lookup-in-member-function-definitions) - [Nominal data classes](#nominal-data-classes) - [Nested type](#nested-type) - [Let](#let) @@ -780,9 +781,14 @@ tl = {.x = 5, .y = 6} as TextLabel; Assert(tl.text == "default"); ``` -### Nested functions +### Member functions -An nested function is like a +Member functions can either be class functions or methods. Class functions are +members of the type, while methods can only be called on instances. + +#### Class functions + +A class function is like a [C++ static member function or method](), and is declared like a function at file scope. The declaration can include a definition of the function body, or that definition can be provided out of line @@ -805,7 +811,7 @@ fn Point.CreateCentered() -> Self { } ``` -Nested functions are members of the type, and may be accessed as using dot `.` +Class functions are members of the type, and may be accessed as using dot `.` member access either the type or any instance. ``` @@ -813,11 +819,11 @@ var p1: Point = Point.Origin(); var p2: Point = p1.CreateCentered(); ``` -### Methods +#### Methods [Method]() declarations are distinguished from other -[nested function declarations](#nested-functions) by having a `me` parameter in +[class function declarations](#class-functions) by having a `me` parameter in square brackets `[`...`]` before the explicit parameter list in parens `(`...`)`. There is no implicit member access in methods, so inside the method body members are accessed through the `me` parameter. Methods may be written @@ -863,15 +869,15 @@ the `me` parameter must be in the same list in square brackets `[`...`]`. The `me` parameter may appear in any position in that list, as long as it appears after any names needed to describe its type. -#### Name lookup in method definitions +#### Name lookup in member function definitions -When defining a method lexically inline, we need to delay type checking until -the definition of the current type is complete. This means that member lookup is -also delayed. That means that you can reference `me.F()` in a lexically inline -method definition even before the declaration of `F` in that class definition. -However, unqualified names still need to be declared before. +When defining a member function lexically inline, we need to delay type checking +until the definition of the current type is complete. This means that member +lookup is also delayed. That means that you can reference `me.F()` in a +lexically inline method definition even before the declaration of `F` in that +class definition. However, unqualified names still need to be declared before. -```diff +``` class Point { fn Distance[me: Self]() -> f32 { // ✅ Allowed: look up of `x` and `y` delayed until From 557f935d4e8c5d9bfab898be898cc2a34b5abe5d Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 15:23:29 -0700 Subject: [PATCH 23/33] Nested type -> member type --- docs/design/classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 6d3bb2a4091c4..1ddeecff3d6f6 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -40,7 +40,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Methods](#methods) - [Name lookup in member function definitions](#name-lookup-in-member-function-definitions) - [Nominal data classes](#nominal-data-classes) - - [Nested type](#nested-type) + - [Member type](#member-type) - [Let](#let) - [Alias](#alias) - [Private access](#private-access) @@ -948,7 +948,7 @@ The fields of data classes must all be public. That line will add **References:** Rationale for this approach is given in proposal [#722](/proposals/p0722.md#nominal-data-class). -### Nested type +### Member type Additional types may be defined in the scope of a class definition. From 00cda76950b82e6ce47dce10414d37b134fd5ece Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 15:38:00 -0700 Subject: [PATCH 24/33] `:!` in let, clarify name lookup --- docs/design/classes.md | 21 ++++++++++----------- proposals/p0722.md | 9 +++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 1ddeecff3d6f6..4863a15e7fbc5 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -871,11 +871,12 @@ after any names needed to describe its type. #### Name lookup in member function definitions -When defining a member function lexically inline, we need to delay type checking -until the definition of the current type is complete. This means that member -lookup is also delayed. That means that you can reference `me.F()` in a -lexically inline method definition even before the declaration of `F` in that -class definition. However, unqualified names still need to be declared before. +When defining a member function lexically inline, we delay type checking of the +function body until the definition of the current type is complete. This means +that member lookup is also delayed. That means that you can reference `me.F()` +in a lexically inline method definition even before the declaration of `F` in +that class definition. However, unqualified names still need to be declared +before. ``` class Point { @@ -971,15 +972,13 @@ Other type constants can be defined using a `let` declaration: ``` class MyClass { - let Pi: f32 = 3.141592653589793; - let IndexType: Type = i32; + let Pi:! f32 = 3.141592653589793; + let IndexType:! Type = i32; } ``` -These do not affect the storage of instances of that class. - -**Open question:** Should these use the `:!` generic syntax decided in -[issue #565](https://github.com/carbon-language/carbon-lang/issues/565)? +The `:!` indicates that this is defining a compile-time constant, and so does +not affect the storage of instances of that class. ### Alias diff --git a/proposals/p0722.md b/proposals/p0722.md index b4b35381d0627..8bbbf6bc38465 100644 --- a/proposals/p0722.md +++ b/proposals/p0722.md @@ -28,6 +28,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Differences between functions and methods](#differences-between-functions-and-methods) - [Specifying linkage as part of the access modifier](#specifying-linkage-as-part-of-the-access-modifier) - [Nominal data class](#nominal-data-class) + - [Let constants](#let-constants) @@ -272,3 +273,11 @@ data classes" in a consistent way. We chose the name `Data` rather than `DataClass` for the interface since tuples implicitly implement the interface and were "product types" rather than "classes". + +### Let constants + +We wanted a consistent interpretation for `let` declarations in classes and +function bodies. Experience from C++ `const int` variables, where we try +constant-evaluation and then give the program different semantics based on +whether such evaluation happened to work, suggested we wanted to instead require +`:!` in all places where a value that's usable during compilation is introduced. From 01ec40b0b71beb1ae86766a2cbff6d46ed3735af Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 11 Aug 2021 15:47:05 -0700 Subject: [PATCH 25/33] Checkpoint progress. --- docs/design/classes.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 4863a15e7fbc5..5412fa24d2649 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -822,12 +822,12 @@ var p2: Point = p1.CreateCentered(); #### Methods [Method]() -declarations are distinguished from other -[class function declarations](#class-functions) by having a `me` parameter in -square brackets `[`...`]` before the explicit parameter list in parens -`(`...`)`. There is no implicit member access in methods, so inside the method -body members are accessed through the `me` parameter. Methods may be written -lexically inline or after the class declaration. +declarations are distinguished from [class function](#class-functions) +declarations by having a `me` parameter in square brackets `[`...`]` before the +explicit parameter list in parens `(`...`)`. There is no implicit member access +in methods, so inside the method body members are accessed through the `me` +parameter. Methods may be written lexically inline or after the class +declaration. ```carbon class Circle { From dadf910d28efa730c982cfcdcb8d0ec4035e9fc4 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 12 Aug 2021 06:03:35 -0700 Subject: [PATCH 26/33] Link encapsulation --- docs/design/classes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 5412fa24d2649..c2ea6fd00ccf4 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -1004,7 +1004,8 @@ Assert(&sp1.first == &sp1.key); By default, all members of a class are fully publicly accessible. Access can be restricted by adding a keyword, called an [access modifier](https://en.wikipedia.org/wiki/Access_modifiers), prior to the -declaration. +declaration. Access modifiers are how Carbon supports +[encapsulation](#encapsulated-types). ```carbon class Point { From 095500260f1e18c27705f066c116e7d66c255db4 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 12 Aug 2021 10:01:38 -0700 Subject: [PATCH 27/33] Member types can be choice types --- docs/design/classes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index c2ea6fd00ccf4..19b454ebcfd15 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -964,7 +964,8 @@ class StringCounts { ``` The inner type is a member of the type, and is given the name -`StringCounts.Node`. +`StringCounts.Node`. This case is called a _member class_ since the type is a +class, but other kinds of type declarations, like choice types, are allowed. ### Let From dccb54e8ff57a77088bef4bacf94cbd7478553c3 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 16 Aug 2021 14:08:52 -0700 Subject: [PATCH 28/33] Clarify Data interface --- docs/design/classes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/design/classes.md b/docs/design/classes.md index 19b454ebcfd15..ec452527490d3 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -946,6 +946,10 @@ class TextLabel { The fields of data classes must all be public. That line will add [field-wise implementations and operations of all interfaces that a struct with the same fields would get by default](#operations-performed-field-wise). +The word `Data` here refers to an empty interface in the Carbon prologue. That +interface would then be part of our +[strategy for defining how other interfaces are implemented for data classes](#interfaces-implemented-for-data-classes). + **References:** Rationale for this approach is given in proposal [#722](/proposals/p0722.md#nominal-data-class). From 795cf97c26d3ba61f5f876d62e1910f9069b4c5d Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 16 Aug 2021 16:43:23 -0700 Subject: [PATCH 29/33] Aliases future work --- docs/design/classes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/design/classes.md b/docs/design/classes.md index ec452527490d3..bd55a29ac8468 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -1004,6 +1004,9 @@ Assert(sp1.first == sp2.key); Assert(&sp1.first == &sp1.key); ``` +**Future work:** This needs to be connected to the broader design of aliases, +once that lands. + ### Private access By default, all members of a class are fully publicly accessible. Access can be From 6e251a2e4c8ac51a52688a0dd9b5ffe2dc5d6550 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 16 Aug 2021 16:53:22 -0700 Subject: [PATCH 30/33] Clarify access needed for assignment --- docs/design/classes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/design/classes.md b/docs/design/classes.md index bd55a29ac8468..e2140211ad452 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -757,6 +757,9 @@ default values for fields, and so may be initialized with a #### Assignment +Assignment to a struct value is also allowed in a function with access to all +the data fields of a class. + Field defaults are only used when initializing a new value, not assigning to an existing variable. When assigning, values for all the fields must be specified. This avoids ambiguity about whether to use the default value or the previous From bdd7363861f5c5ef87cdb13b8e8467cdd889757a Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 16 Aug 2021 17:36:08 -0700 Subject: [PATCH 31/33] Resolve some questions --- docs/design/classes.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index e2140211ad452..bf4b732790ae6 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -758,12 +758,8 @@ default values for fields, and so may be initialized with a #### Assignment Assignment to a struct value is also allowed in a function with access to all -the data fields of a class. - -Field defaults are only used when initializing a new value, not assigning to an -existing variable. When assigning, values for all the fields must be specified. -This avoids ambiguity about whether to use the default value or the previous -value for a field. +the data fields of a class. Assignment always overwrites all of the field +members. ``` var tl: TextLabel = {.x = 1, .y = 2}; @@ -772,18 +768,25 @@ Assert(tl.text == "default"); // ✅ Allowed: assigns all fields tl = {.x = 3, .y = 4, .text = "new"}; -// ❌ Forbidden: should tl.text == "default" or "new"? -tl = {.x = 5, .y = 6}; - // ✅ Allowed: This statement is evaluated in two steps: // 1. {.x = 5, .y = 6} is converted into a new TextLabel value, // using default for field `text`. // 2. tl is assigned to a TextLabel, which has values for all // fields. -tl = {.x = 5, .y = 6} as TextLabel; +tl = {.x = 5, .y = 6}; Assert(tl.text == "default"); ``` +**Open question:** This behavior might be surprising because there is an +ambiguity about whether to use the default value or the previous value for a +field. We could require all fields to be specified when assigning, and only use +field defaults when initializing a new value. + +``` +// ❌ Forbidden: should tl.text == "default" or "new"? +tl = {.x = 5, .y = 6}; +``` + ### Member functions Member functions can either be class functions or methods. Class functions are @@ -913,15 +916,23 @@ class Point { return Create(xy, xy); } + fn CreateXAxis(x: f32) -> Point; + fn Angle[me: Self]() -> f32; var x: f32; var y: f32; } +fn Point.CreateXAxis(x: f32) -> Point; + // ✅ Allowed: `Point` type is complete. + // Members of `Point` like `Create` are in scope. + return Create(x, 0); +} + fn Point.Angle[me: Self]() -> f32 { - // ✅ Allowed: `Point` type is complete, - // function is checked immediately. + // ✅ Allowed: `Point` type is complete. + // Function is checked immediately. return Math.ATan2(me.y, me.x); } ``` From 1f92bdfda7dd145671f516e210724fd2ced1d9fa Mon Sep 17 00:00:00 2001 From: josh11b Date: Mon, 16 Aug 2021 18:17:24 -0700 Subject: [PATCH 32/33] Apply suggestions from code review Co-authored-by: Chandler Carruth --- docs/design/classes.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index bf4b732790ae6..0346f05d9a654 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -879,16 +879,17 @@ after any names needed to describe its type. When defining a member function lexically inline, we delay type checking of the function body until the definition of the current type is complete. This means -that member lookup is also delayed. That means that you can reference `me.F()` +that name lookup *for members of objects* is also delayed. That means that you can reference `me.F()` in a lexically inline method definition even before the declaration of `F` in -that class definition. However, unqualified names still need to be declared -before. +that class definition. However, other names still need to be declared +before they are used. This includes unqualified names, names within namespaces, +and names *for members of types*. ``` class Point { fn Distance[me: Self]() -> f32 { - // ✅ Allowed: look up of `x` and `y` delayed until - // `type_of(me) == Self` is complete. + // ✅ Allowed: `x` and `y` are names for members of an object, + // and so lookup is delayed until `type_of(me) == Self` is complete. return Math.Sqrt(me.x * me.x + me.y * me.y); } @@ -897,11 +898,11 @@ class Point { return Create(r * Math.Cos(theta), r * Math.Sin(theta)); } fn CreatePolarValid1(r: f32, theta: f32) -> Point { - // ✅ Allowed: look up of `Create` delayed until `Point` is complete. + // ❌ Forbidden: `Create` is not yet declared. return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta)); } fn CreatePolarValid2(r: f32, theta: f32) -> Point { - // ✅ Allowed: look up of `Create` delayed until `Self` is complete. + // ❌ Forbidden: `Create` is not yet declared. return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta)); } From c73507ab71eaee80ea3a9c11eef40216ea2087fc Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 16 Aug 2021 18:18:07 -0700 Subject: [PATCH 33/33] Fix formatting --- docs/design/classes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 0346f05d9a654..09b33799785a0 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -879,11 +879,11 @@ after any names needed to describe its type. When defining a member function lexically inline, we delay type checking of the function body until the definition of the current type is complete. This means -that name lookup *for members of objects* is also delayed. That means that you can reference `me.F()` -in a lexically inline method definition even before the declaration of `F` in -that class definition. However, other names still need to be declared -before they are used. This includes unqualified names, names within namespaces, -and names *for members of types*. +that name lookup _for members of objects_ is also delayed. That means that you +can reference `me.F()` in a lexically inline method definition even before the +declaration of `F` in that class definition. However, other names still need to +be declared before they are used. This includes unqualified names, names within +namespaces, and names _for members of types_. ``` class Point {