Allow unqualified name lookup

Pull request

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

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.