Allow unqualified name lookup
Table of contents
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 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 and information accumulation principle affect this.
This will also work similarly to unqualified lookup within C++.
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.
Classes
This proposal updates the class design to address classes.
Interfaces
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 has not been fleshed out, this won’t make much of an update to it.
An example for namespaces would be:
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 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 asks how unqualified lookup should work for impl
. The generics design 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
- 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
- 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
andpackage.Foo.a
are defined butpackage.Foo.a
is hidden in code whilepackage.a
is easy to find. It’s likely thatpackage.Foo.a
would be considered unambiguous for unqualified name lookup.
- For example, name lookup results could be mildly confusing if both
Disadvantages:
- Very verbose, and could prove un-ergonomic for developers.