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.
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.
The member access design and information accumulation principle affect this.
This will also work similarly to unqualified lookup within C++.
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.
This proposal updates the class design to address classes.
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);
}
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;
}
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.
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.
- 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.
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.