From 6c812db05cdbe63ddafd078885cc22b0bba7a66e Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Wed, 22 Nov 2023 14:52:08 -0800 Subject: [PATCH] Clarify name bindings in namespaces. (#3407) - Require namespace members be declared in the same name scope as the namespace is declared. - Allow binding patterns to directly declare names in namespaces. - Disallow using different namespaces in the same binding pattern. --------- Co-authored-by: Chandler Carruth Co-authored-by: Richard Smith --- .../code_and_name_organization/README.md | 64 +++++- proposals/p3407.md | 201 ++++++++++++++++++ 2 files changed, 261 insertions(+), 4 deletions(-) create mode 100644 proposals/p3407.md diff --git a/docs/design/code_and_name_organization/README.md b/docs/design/code_and_name_organization/README.md index f183701009a6..f9a92dda233e 100644 --- a/docs/design/code_and_name_organization/README.md +++ b/docs/design/code_and_name_organization/README.md @@ -33,6 +33,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Imports from the current package](#imports-from-the-current-package) - [Namespaces](#namespaces) - [Re-declaring imported namespaces](#re-declaring-imported-namespaces) + - [Declaring namespace members](#declaring-namespace-members) - [Aliasing](#aliasing) - [Caveats](#caveats) - [Package and library name conflicts](#package-and-library-name-conflicts) @@ -618,10 +619,11 @@ fn GetArea(c: Circle) { ... } ### Namespaces -Namespaces offer named paths for entities. Namespaces may be nested. Multiple -libraries may contribute to the same namespace. In practice, packages may have -namespaces such as `Testing` containing entities that benefit from an isolated -space but are present in many libraries. +Namespaces offer named paths for entities. Namespaces must be declared at file +scope, and may be nested. Multiple libraries may contribute to the same +namespace. In practice, packages may have namespaces such as `Testing` +containing entities that benefit from an isolated space but are present in many +libraries. The `namespace` keyword's syntax may loosely be expressed as a regular expression: @@ -671,6 +673,54 @@ namespace Shapes; struct Shapes.Square { ... }; ``` +#### Declaring namespace members + +Namespace members may only be declared in the same name scope which was used to +declare the namespace. For example: + +```carbon +namespace NS; + +// ✅ Allowed: declaration is in file scope, which also declared `NS`. +class NS.ClassT { + // ❌ Error: A class body has its own name scope. + var NS.a: i32 = 0; +} + +fn Function() { + // ❌ Error: A function body has its own name scope. + var NS.b: i32 = 1; +} + +// ✅ Allowed: declaration is in file scope, which also declared `NS`. +namespace NS.MemberNS; + +// ✅ Allowed: declaration is in file scope, which also declared `NS.MemberNS`. +class NS.MemberNS.MemberClassT {} +``` + +When multiple names are declared by binding patterns in the same pattern, all +names must be in the same namespace. Because namespace members can only be +declared in the same scope as the namespace, a namespace-qualified pattern +binding can only be used in the pattern of a `var` or `let` declaration. For +example: + +```carbon +namespace NS; + +// ✅ Allowed: `a` and `b` use the default namespace. +var (a: i32, b: i32) = (1, 2); + +// ✅ Allowed: `c` and `d` are in the same namespace. +var (NS.c: i32, NS.d: i32) = (3, 4); + +// ❌ Error: `e` and `f` are not in the same namespace. +var (e: i32, NS.f: i32) = (5, 6); +``` + +This restriction only applies when declaring names in binding patterns, not +other name uses in patterns. + #### Aliasing Carbon's [alias keyword](/docs/design/aliases.md) will support aliasing @@ -969,6 +1019,10 @@ should be part of a larger testing plan. - Namespaces - [File-level namespaces](/proposals/p0107.md#file-level-namespaces) - [Scoped namespaces](/proposals/p0107.md#scoped-namespaces) + - [Allow prefixing a tuple binding pattern with a namespace](/proposals/p3407.md#allow-prefixing-a-tuple-binding-pattern-with-a-namespace) + - [Allow binding patterns to declare names in multiple namespaces](/proposals/p3407.md#allow-binding-patterns-to-declare-names-in-multiple-namespaces) + - [Allow declaring names in namespaces not owned by the current scope](/proposals/p3407.md#allow-declaring-names-in-namespaces-not-owned-by-the-current-scope) + - [Allow declaring namespaces in scopes other than the file scope](/proposals/p3407.md#allow-declaring-namespaces-in-scopes-other-than-the-file-scope) ## References @@ -978,3 +1032,5 @@ should be part of a larger testing plan. [#2550: Simplified package declaration for the main package](https://github.com/carbon-language/carbon-lang/pull/2550) - Proposal [#3403: Change Main//default to an api file](https://github.com/carbon-language/carbon-lang/pull/3403) +- Proposal + [#3453: Clarify name bindings in namespaces.](https://github.com/carbon-language/carbon-lang/pull/3407) diff --git a/proposals/p3407.md b/proposals/p3407.md new file mode 100644 index 000000000000..3c6bd966731d --- /dev/null +++ b/proposals/p3407.md @@ -0,0 +1,201 @@ +# Clarify name bindings in namespaces. + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/3407) + + + +## Table of contents + +- [Abstract](#abstract) +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Rationale](#rationale) +- [Alternatives considered](#alternatives-considered) + - [Allow prefixing a tuple binding pattern with a namespace](#allow-prefixing-a-tuple-binding-pattern-with-a-namespace) + - [Allow binding patterns to declare names in multiple namespaces](#allow-binding-patterns-to-declare-names-in-multiple-namespaces) + - [Allow declaring names in namespaces not owned by the current scope](#allow-declaring-names-in-namespaces-not-owned-by-the-current-scope) + - [Allow declaring namespaces in scopes other than the file scope](#allow-declaring-namespaces-in-scopes-other-than-the-file-scope) + + + +## Abstract + +- Require namespace members be declared in the same name scope as the + namespace is declared. + - Declaring namespaces outside file scope is disallowed, so this means + only at file scope. +- Allow binding patterns to directly declare names in namespaces. +- Disallow introducing bindings in different namespaces in the same pattern. + +## Problem + +While the trivial case of `class NS.C` seems to be supported as a consequence of +proposal +[#107: Code and name organization](https://github.com/carbon-language/carbon-lang/pull/107), +it lacks detail. For example, there are a couple syntactic options when binding +multiple names. + +Also, there's no clear decision around code such as: + +```carbon +namespace NS; +class ClassT { + // Is this a class member accessed through `NS`, or a file scope member inside + // `NS`? What is its lifetime? + var NS.a: i32 = 0; +} +``` + +This proposal mainly aims to remove ambiguities. + +## Background + +Namespaces are covered in +[code and name organization](/docs/design/code_and_name_organization/#namespaces). +Binding patterns are covered in +[pattern matching](/docs/design/pattern_matching.md#binding-patterns). + +There's some discussion of `var` in +[values, variables, and pointers](/docs/design/values.md), but it's specific to +locals. That doesn't address other use cases, such as globals or member +variables. + +## Proposal + +When used to declare names in binding patterns, as in `var` or `let`, all names +must be in the same namespace. `namespace` members must be declared from within +the same scope that declared the `namespace`. + +There was uncertainty about whether namespaces could be declared outside of file +scopes; for now, disallow it. + +See the changes to +[code and name organization](/docs/design/code_and_name_organization/#namespaces) +for reference. + +## Rationale + +- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) + + - Requiring that declarations of multiple names use `NS.a` syntax is + consistent with the single variable case. + - Requiring namespace members be declared while in the same name scope as + the namespace itself makes lifetimes clearer. + +## Alternatives considered + +### Allow prefixing a tuple binding pattern with a namespace + +We could use the namespace to prefix the binding tuple. For example: + +```carbon +var NS.(a: i32, b: i32) = (3, 4); +``` + +It's rare that we would have a single statement declare multiple names. As a +consequence, the separation of the namespace qualifier from the declared +identifier might end up unique to this syntax. In that context, we prefer `NS.a` +for consistency with other cases, such as `class NS.class`. + +### Allow binding patterns to declare names in multiple namespaces + +We could allow binding patterns to declare names in multiple namespaces. For +example: + +```carbon +namespace NS; +var (NS.a: i32, b: i32) = InitData(); +``` + +Mixing namespaces could be confusing: for example, `b` could be misunderstood to +be declared in `NS`. We lack data that would demonstrate benefits to offset +that. + +We disallow mixing namespaces in a single declaration for simplicity. + +### Allow declaring names in namespaces not owned by the current scope + +We could allow declaring names in namespaces not owned by the current scope. For +example: + +```carbon +namespace NS; +class ClassT { + var NS.val: i32; + + class NS.ChildT {} +} +``` + +Here, `package.NS.val` would be a global, but `ClassT.NS.val` looks more like an +instance member. It's also unclear whether `ClassT.NS.val` (or +`instance.NS.val`) could be used to reference the produced variable, since `NS` +is not inside `ClassT`'s name scope. The naming problems extend to non-binding +declarations such as `NS.ChildT`, too. + +Disallowing using namespaces to cross name scopes is consistent with rules that +generally prevent declaring names in other name scopes, such as: + +```carbon +class A { + class B { + // `C` must be declared directly inside `A`. + class A.C; + } +} + +// `D` must be declared within `A`, even if separately defined. +class A.D {} +``` + +Both the `namespace` declaration and names declared within the `namespace` must +be written in the same name scope. This avoids name lookup ambiguities, and +builds consistency in name scope boundaries across declarations. + +### Allow declaring namespaces in scopes other than the file scope + +We could allow declaring namespaces in scopes other than the file scope. It's +ambiguous what path should have resulted from proposal +[#107: Code and name organization](https://github.com/carbon-language/carbon-lang/pull/107), +although examples all focus on file scope, so other scopes weren't carefully +considered. + +It might prove useful in some situations. For example, perhaps a complex class +would find a member namespace useful: + +```carbon +class Complex { + namespace OptionSet1; + class OptionSet1.MemberClassA; + class OptionSet1.MemberClassB; + + namespace OptionSet2; + class OptionSet2.MemberClassC; + class OptionSet2.MemberClassD; + + namespace Vars; + var Vars.a; +} +``` + +This proposal takes a stance against declaring namespaces other than the file +scope because: + +- Proposal + [#107: Code and name organization](https://github.com/carbon-language/carbon-lang/pull/107) + only mentions file scope namespaces, implicitly disallowing it in other + scopes. +- Disallowing namespaces in other scopes is consistent with C++. + +If allowed, it would be necessary to decide whether `Complex.Vars.a` would have +instance or global lifetime. + +For now, namespaces may only be declared at file scope, which gives consistency +with C++. This decision may be reevaluated in a future proposal.