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 <chandlerc@gmail.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
Jon Ross-Perkins
2023-11-22 22:52:08 +00:00
committed by GitHub
co-authored by Chandler Carruth Richard Smith
parent af9b323aad
commit 6c812db05c
2 changed files with 261 additions and 4 deletions
@@ -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)
+201
View File
@@ -0,0 +1,201 @@
# Clarify name bindings in namespaces.
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
[Pull request](https://github.com/carbon-language/carbon-lang/pull/3407)
<!-- toc -->
## 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)
<!-- tocstop -->
## 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.