mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:50:14 +01:00
Allow unqualified name lookup for class members (#2287)
Allow unqualified name lookup in multiple situations:
- For classes and interfaces, whether inside the class scope or within an
out-of-line function definition.
- For namespaces, when the namespace is used in a declaration.
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
co-authored by
Richard Smith
parent
4885a2bf8e
commit
14cc716ddc
+111
-43
@@ -37,7 +37,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Member functions](#member-functions)
|
||||
- [Class functions](#class-functions)
|
||||
- [Methods](#methods)
|
||||
- [Name lookup in member function definitions](#name-lookup-in-member-function-definitions)
|
||||
- [Deferred member function definitions](#deferred-member-function-definitions)
|
||||
- [Name lookup in classes](#name-lookup-in-classes)
|
||||
- [Nominal data classes](#nominal-data-classes)
|
||||
- [Member type](#member-type)
|
||||
- [Let](#let)
|
||||
@@ -914,71 +915,122 @@ 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 member function definitions
|
||||
#### Deferred member function definitions
|
||||
|
||||
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_.
|
||||
When defining a member function lexically inline, the body is deferred and
|
||||
processed as if it appeared immediately after the end of the outermost enclosing
|
||||
class, like in C++.
|
||||
|
||||
```
|
||||
For example, given a class with inline function definitions:
|
||||
|
||||
```carbon
|
||||
class Point {
|
||||
fn Distance[me: Self]() -> f32 {
|
||||
// ✅ 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);
|
||||
}
|
||||
|
||||
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 {
|
||||
// ❌ Forbidden: `Create` is not yet declared.
|
||||
return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta));
|
||||
}
|
||||
fn CreatePolarValid2(r: f32, theta: f32) -> Point {
|
||||
// ❌ Forbidden: `Create` is not yet declared.
|
||||
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);
|
||||
}
|
||||
var x: f32;
|
||||
var y: f32;
|
||||
}
|
||||
```
|
||||
|
||||
fn CreateXAxis(x: f32) -> Point;
|
||||
These are all parsed as if they were defined outside the class scope:
|
||||
|
||||
fn Angle[me: Self]() -> f32;
|
||||
```carbon
|
||||
class Point {
|
||||
fn Distance[me: Self]() -> f32;
|
||||
fn Create(x: f32, y: f32) -> Point;
|
||||
|
||||
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.Distance[me: Self]() -> f32 {
|
||||
return Math.Sqrt(me.x * me.x + me.y * me.y);
|
||||
}
|
||||
|
||||
fn Point.Angle[me: Self]() -> f32 {
|
||||
// ✅ Allowed: `Point` type is complete.
|
||||
// Function is checked immediately.
|
||||
return Math.ATan2(me.y, me.x);
|
||||
fn Point.Create(x: f32, y: f32) -> Point {
|
||||
return {.x = x, .y = y};
|
||||
}
|
||||
```
|
||||
|
||||
**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).
|
||||
#### Name lookup in classes
|
||||
|
||||
[Member access](expressions/member_access.md) is an expression; details are
|
||||
covered there. Because function definitions are
|
||||
[deferred](#deferred-member-function-definitions), name lookup in classes works
|
||||
the same regardless of whether a function is inline. The class body forms a
|
||||
scope for name lookup, and function definitions can perform unqualified name
|
||||
lookup within that scope.
|
||||
|
||||
For example:
|
||||
|
||||
```carbon
|
||||
class Square {
|
||||
fn GetArea[me: Self]() -> f32 {
|
||||
// ✅ OK: performs name lookup on `me`.
|
||||
return me.size * me.size;
|
||||
// ❌ Error: finds `Square.size`, but an instance is required.
|
||||
return size * size;
|
||||
// ❌ Error: an instance is required.
|
||||
return Square.size * Square.size;
|
||||
// ✅ OK: performs instance binding with `me`.
|
||||
return me.(Square.size) * me.(Square.size);
|
||||
// ✅ OK: uses unqualified name lookup to find `Square.size`, then performs
|
||||
// instance binding with `me`.
|
||||
return me.(size) * me.(size);
|
||||
}
|
||||
|
||||
fn GetDoubled[me: Self]() -> Square {
|
||||
// ✅ OK: performs name lookup on `Square` for `Create`.
|
||||
return Square.Create(me.size);
|
||||
// ✅ OK: performs unqualified name lookup within class scope for `Create`.
|
||||
return Create(me.size);
|
||||
// ✅ OK: performs name lookup on `me` for `Create`.
|
||||
return me.Create(me.size);
|
||||
}
|
||||
|
||||
fn Create(size: f32) -> Square;
|
||||
|
||||
var size: f32;
|
||||
}
|
||||
```
|
||||
|
||||
The example's name lookups refer to `Create` and `size` which are defined after
|
||||
the example member access; this is valid because of
|
||||
[deferred member function definitions](#deferred-member-function-definitions).
|
||||
|
||||
However, function signatures must still complete lookup without deferring. For
|
||||
example:
|
||||
|
||||
```carbon
|
||||
class List {
|
||||
// ❌ Error: `Iterator` has not yet been defined.
|
||||
fn Iterate() -> Iterator;
|
||||
|
||||
class Iterator {
|
||||
...
|
||||
}
|
||||
|
||||
// ✅ OK: The definition of Iterator is now available.
|
||||
fn Iterate() -> Iterator;
|
||||
}
|
||||
```
|
||||
|
||||
An out-of-line function definition's parameters, return type, and body are
|
||||
evaluated as if in-scope. For example:
|
||||
|
||||
```carbon
|
||||
// ✅ OK: The return type performs unqualified name lookup into `List` for
|
||||
// `Iterator`.
|
||||
fn List.Iterate() -> Iterator {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Nominal data classes
|
||||
|
||||
@@ -2144,6 +2196,16 @@ the type of `U.x`."
|
||||
- [Separate "exact" and "or derived" variations on types](/proposals/p0777.md#separate-exact-and-or-derived-variations-on-types)
|
||||
- [Separate "exact" and "or derived" variations on pointers](/proposals/p0777.md#separate-exact-and-or-derived-variations-on-pointers)
|
||||
|
||||
- [#875: Principle: Information accumulation](https://github.com/carbon-language/carbon-lang/pull/875)
|
||||
|
||||
- Allow information to be used before it is provided
|
||||
[globally](/proposals/p0875.md#strict-global-consistency),
|
||||
[within a file](/proposals/p0875.md#context-sensitive-local-consistency),
|
||||
or
|
||||
[within a top-level declaration](/proposals/p0875.md#top-down-with-minimally-deferred-type-checking).
|
||||
- [Do not allow inline method bodies to use members before they are declared](/proposals/p0875.md#strict-top-down)
|
||||
- [Do not allow separate declaration and definition](/proposals/p0875.md#disallow-separate-declaration-and-definition)
|
||||
|
||||
- [#981: Implicit conversions for aggregates](https://github.com/carbon-language/carbon-lang/pull/981)
|
||||
|
||||
- [Field order is not significant](/proposals/p0981.md#field-order-is-not-significant)
|
||||
@@ -2166,12 +2228,18 @@ the type of `U.x`."
|
||||
- [Make `Self` a member of all types](/proposals/p2107.md#make-self-a-member-of-all-types)
|
||||
- [`where` operator could be associative](/proposals/p2107.md#where-operator-could-be-associative)
|
||||
|
||||
- [#2287: Allow unqualified name lookup for class members](https://github.com/carbon-language/carbon-lang/pull/2287)
|
||||
|
||||
- [No unqualified lookup when defining outside a scope](/proposals/p2287.md#no-unqualified-lookup-when-defining-outside-a-scope)
|
||||
|
||||
## References
|
||||
|
||||
- [#257: Initialization of memory and variables](https://github.com/carbon-language/carbon-lang/pull/257)
|
||||
- [#561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561)
|
||||
- [#722: Nominal classes and methods](https://github.com/carbon-language/carbon-lang/pull/722)
|
||||
- [#777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777)
|
||||
- [#875: Principle: Information accumulation](https://github.com/carbon-language/carbon-lang/pull/875)
|
||||
- [#981: Implicit conversions for aggregates](https://github.com/carbon-language/carbon-lang/pull/981)
|
||||
- [#1154: Destructors](https://github.com/carbon-language/carbon-lang/pull/1154)
|
||||
- [#2107: Clarify rules around `Self` and `.Self`](https://github.com/carbon-language/carbon-lang/pull/2107)
|
||||
- [#2287: Allow unqualified name lookup for class members](https://github.com/carbon-language/carbon-lang/pull/2287)
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# Allow unqualified name lookup
|
||||
|
||||
<!--
|
||||
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/####)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Abstract](#abstract)
|
||||
- [Problem](#problem)
|
||||
- [Background](#background)
|
||||
- [Proposal](#proposal)
|
||||
- [Classes](#classes)
|
||||
- [Interfaces](#interfaces)
|
||||
- [Namespaces](#namespaces)
|
||||
- [Open question](#open-question)
|
||||
- [Implicit instance binding to `me`](#implicit-instance-binding-to-me)
|
||||
- [Out-of-line definitions for impls](#out-of-line-definitions-for-impls)
|
||||
- [Rationale](#rationale)
|
||||
- [Alternatives considered](#alternatives-considered)
|
||||
- [No unqualified lookup when defining outside a scope](#no-unqualified-lookup-when-defining-outside-a-scope)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Abstract
|
||||
|
||||
Allow unqualified name lookup in multiple situations:
|
||||
|
||||
- For classes and interfaces, whether inside the class scope or within an
|
||||
out-of-line function definition.
|
||||
- For namespaces, when the namespace is used in a declaration.
|
||||
|
||||
## Problem
|
||||
|
||||
[Member access](/docs/design/expressions/member_access.md) defines certain
|
||||
member access behaviors. However, it doesn't cover what happens if an
|
||||
unqualified name lookup occurs within a class, particularly for an out-of-line
|
||||
member function definition, or other situations.
|
||||
|
||||
## Background
|
||||
|
||||
The [member access design](/docs/design/expressions/member_access.md) and
|
||||
[information accumulation principle](/docs/project/principles/information_accumulation.md)
|
||||
affect this.
|
||||
|
||||
This will also work similarly to
|
||||
[unqualified lookup within C++](https://en.cppreference.com/w/cpp/language/unqualified_lookup).
|
||||
|
||||
## Proposal
|
||||
|
||||
Allow unqualified name lookup which will use the appropriate scope.
|
||||
|
||||
Implicit instance binding to `me` is not proposed; it is left as an
|
||||
[open question](#implicit-instance-binding-to-me).
|
||||
|
||||
### Classes
|
||||
|
||||
This proposal updates [the class design](/docs/design/classes.md) to address
|
||||
classes.
|
||||
|
||||
### Interfaces
|
||||
|
||||
```carbon
|
||||
interface Vector {
|
||||
fn Scale[me: Self](v: f64) -> Self;
|
||||
// Default definition of `Invert` calls `Scale`.
|
||||
default fn Invert[me: Self]() -> Self;
|
||||
}
|
||||
|
||||
// `Self` is valid here because it's doing unqualified name lookup into
|
||||
// `Vector`.
|
||||
default fn Vector.Invert[me: Self]() -> Self {
|
||||
// `Scale` is valid here because it does unqualified name lookup into
|
||||
// `Vector`, then an instance binding with `me`.
|
||||
return me.(Scale)(-1.0);
|
||||
}
|
||||
```
|
||||
|
||||
### Namespaces
|
||||
|
||||
More generally, this should also be true of other scopes used in declarations.
|
||||
In particular, namespaces should also follow the same rule. However, since
|
||||
[name lookup](/docs/design/name_lookup.md) has not been fleshed out, this won't
|
||||
make much of an update to it.
|
||||
|
||||
An example for namespaces would be:
|
||||
|
||||
```carbon
|
||||
namespace Foo;
|
||||
var Foo.a: i32 = 0;
|
||||
|
||||
class Foo.B {}
|
||||
|
||||
// `B` and `a` are valid here because unqualified name lookup occurs within
|
||||
// `Foo`.
|
||||
fn Foo.C(B b) -> i32 {
|
||||
return a;
|
||||
}
|
||||
```
|
||||
|
||||
## Open question
|
||||
|
||||
### Implicit instance binding to `me`
|
||||
|
||||
In C++, unqualified name lookup can implicitly do instance binding to `this`. In
|
||||
other words, `this->Member()` and `Member()` behave similarly inside a method
|
||||
definition.
|
||||
|
||||
In Carbon, the current design hasn't fleshed out whether `me` would behave
|
||||
similarly. Most design documentation assumes it will not, but it hasn't been
|
||||
directly considered in a proposal, and
|
||||
[implicit scoped function parameters](https://github.com/carbon-language/carbon-lang/issues/1974)
|
||||
might offer a way to make it work in a language-consistent manner.
|
||||
|
||||
This proposal takes no stance on unqualified name lookup resolving `me`: it is
|
||||
not intended to change behavior from previous proposals.
|
||||
|
||||
### Out-of-line definitions for impls
|
||||
|
||||
Issue [#2377](https://github.com/carbon-language/carbon-lang/issues/2377) asks
|
||||
how unqualified lookup should work for `impl`. The
|
||||
[generics design](/docs/design/generics/details.md) also doesn't appear to give
|
||||
syntax for out-of-line definitions of other impls.
|
||||
|
||||
## Rationale
|
||||
|
||||
- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
|
||||
- Performing unqualified name lookup for class members should be fairly
|
||||
unsurprising to readers, and should allow for more concise code when
|
||||
working within a namespace.
|
||||
- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code)
|
||||
- This behavior will be similar to how C++ works.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
### No unqualified lookup when defining outside a scope
|
||||
|
||||
We could decide not to support unqualified lookup when defining something that
|
||||
is presented within the top-level scope of the file.
|
||||
|
||||
Note this has subtle implications. If `Foo.C` in the namespace example is
|
||||
considered to be outside the `Foo` scope for this purpose, it means the function
|
||||
would need to look like:
|
||||
|
||||
```
|
||||
fn Foo.C(Foo.B b) -> i32 {
|
||||
return Foo.a;
|
||||
}
|
||||
```
|
||||
|
||||
It could also mean that, on a class, an inline declaration
|
||||
`fn Foo() -> ClassMember` is valid, while an out-of-line definition
|
||||
`fn Class.Foo() -> ClassMember` is not, requiring `Class.ClassMember`.
|
||||
|
||||
Advantages:
|
||||
|
||||
- Explicit in access.
|
||||
- For example, name lookup results could be mildly confusing if both
|
||||
`package.a` and `package.Foo.a` are defined but `package.Foo.a` is
|
||||
hidden in code while `package.a` is easy to find. It's likely that
|
||||
`package.Foo.a` would be considered unambiguous for unqualified name
|
||||
lookup.
|
||||
|
||||
Disadvantages:
|
||||
|
||||
- Very verbose, and could prove un-ergonomic for developers.
|
||||
Reference in New Issue
Block a user