Kaynağa Gözat

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>
Jon Ross-Perkins 3 yıl önce
ebeveyn
işleme
14cc716ddc
2 değiştirilmiş dosya ile 283 ekleme ve 43 silme
  1. 111 43
      docs/design/classes.md
  2. 172 0
      proposals/p2287.md

+ 111 - 43
docs/design/classes.md

@@ -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.Create(x: f32, y: f32) -> Point {
+  return {.x = x, .y = y};
+}
+```
+
+#### 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;
 
-fn Point.Angle[me: Self]() -> f32 {
-  // ✅ Allowed: `Point` type is complete.
-  // Function is checked immediately.
-  return Math.ATan2(me.y, me.x);
+  class Iterator {
+    ...
+  }
+
+  // ✅ OK: The definition of Iterator is now available.
+  fn Iterate() -> Iterator;
 }
 ```
 
-**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).
+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)

+ 172 - 0
proposals/p2287.md

@@ -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.