|
|
@@ -0,0 +1,230 @@
|
|
|
+# Qualified lookup into types being defined
|
|
|
+
|
|
|
+<!--
|
|
|
+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/5087)
|
|
|
+
|
|
|
+<!-- toc -->
|
|
|
+
|
|
|
+## Table of contents
|
|
|
+
|
|
|
+- [Abstract](#abstract)
|
|
|
+- [Problem](#problem)
|
|
|
+- [Background](#background)
|
|
|
+- [Proposal](#proposal)
|
|
|
+- [Details](#details)
|
|
|
+ - [Uses requiring a complete type](#uses-requiring-a-complete-type)
|
|
|
+ - [Lookups into an incomplete generic](#lookups-into-an-incomplete-generic)
|
|
|
+- [Rationale](#rationale)
|
|
|
+- [Alternatives considered](#alternatives-considered)
|
|
|
+ - [Require completeness for qualified name lookup](#require-completeness-for-qualified-name-lookup)
|
|
|
+ - [Do not require a definition for name lookup](#do-not-require-a-definition-for-name-lookup)
|
|
|
+
|
|
|
+<!-- tocstop -->
|
|
|
+
|
|
|
+## Abstract
|
|
|
+
|
|
|
+Allow qualified name lookup into classes and interfaces as soon as we reach the
|
|
|
+`{` of the definition, rather than disallowing such lookups until we reach the
|
|
|
+`}`.
|
|
|
+
|
|
|
+## Problem
|
|
|
+
|
|
|
+We allow unqualified lookups within a type definition to find names that were
|
|
|
+already declared, but not qualified lookups:
|
|
|
+
|
|
|
+```carbon
|
|
|
+class A {
|
|
|
+ class B {}
|
|
|
+ // ✅ OK, `B` names `A.B`.
|
|
|
+ fn F() -> B;
|
|
|
+ // ❌ Error (before this proposal): `A` is not complete.
|
|
|
+ fn G() -> A.B;
|
|
|
+}
|
|
|
+
|
|
|
+interface I {
|
|
|
+ let T:! type;
|
|
|
+ // ✅ OK, `T` names `Self.T`.
|
|
|
+ fn F() -> T;
|
|
|
+ // ❌ Error (before this proposal): type `I` of `Self` is not complete.
|
|
|
+ fn G() -> Self.T;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+This is inconsistent and prevents useful code:
|
|
|
+
|
|
|
+```carbon
|
|
|
+interface Container {
|
|
|
+ let ValueType:! type
|
|
|
+ // ❌ Error (before this proposal): type `Container` of `.Self`
|
|
|
+ // is not complete in implicit access to `.Self.ValueType`.
|
|
|
+ // Implicit access to `Self.ValueType` is OK though.
|
|
|
+ let SliceType:! Container where .ValueType == ValueType;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+An additional inconsistency is that namespaces already support qualified lookup
|
|
|
+before the full list of names is known -- indeed, the full list of names in a
|
|
|
+namespace is never known.
|
|
|
+
|
|
|
+## Background
|
|
|
+
|
|
|
+Proposal
|
|
|
+[#3763](https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p3763.md)
|
|
|
+introduces a name poisoning rule:
|
|
|
+
|
|
|
+> In a declarative scope, it is an error if a name is first looked up and not
|
|
|
+> found, and later introduced.
|
|
|
+
|
|
|
+This is achieved by _poisoning_ a name in a scope when we perform a failed
|
|
|
+lookup for that name in that scope, and diagnosing if the name is later declared
|
|
|
+in a scope where it is poisoned.
|
|
|
+
|
|
|
+With that rule, there is no risk in allowing name lookups into a scope to
|
|
|
+succeed even before the scope is complete. If the name lookup's meaning would be
|
|
|
+changed by a later declaration, an error is issued.
|
|
|
+
|
|
|
+The motivation for this proposal and the proposed rule change were discussed in
|
|
|
+open discussion on
|
|
|
+[2025-02-21](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.ix77am1xk6po),
|
|
|
+[2025-02-27a](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.ank09kkr0tnn),
|
|
|
+and
|
|
|
+[2025-03-03b](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.19yyjdek3asm).
|
|
|
+
|
|
|
+## Proposal
|
|
|
+
|
|
|
+Allow name lookups into a type once it is defined, even before it is complete.
|
|
|
+
|
|
|
+## Details
|
|
|
+
|
|
|
+We adopt the following terminology:
|
|
|
+
|
|
|
+- An entity is _defined_ at the point where we reach the `{` of its
|
|
|
+ definition. _Exception:_ A namespace is defined by its first declaration.
|
|
|
+- An entity is _complete_ at the point where we reach the `}` of its
|
|
|
+ definition.
|
|
|
+
|
|
|
+Instead of qualified name lookup into the scope of a type requiring the type to
|
|
|
+be complete, we now only require it to be defined. Therefore, qualified lookup
|
|
|
+within the braces of the type definition are now permitted.
|
|
|
+
|
|
|
+Such a name lookup only finds names that were declared prior to the lookup, in
|
|
|
+line with the
|
|
|
+[information accumulation principle](/docs/project/principles/information_accumulation.md).
|
|
|
+If the name is first declared after the point at which it is looked up, the
|
|
|
+later declaration of the name is rejected by to the poisoning rule [described earlier](#background].
|
|
|
+
|
|
|
+```carbon
|
|
|
+base class A {
|
|
|
+ class Inner {}
|
|
|
+}
|
|
|
+
|
|
|
+class B {
|
|
|
+ extend base: A;
|
|
|
+ // ✅ OK for now, `Inner` names `A.Inner` because
|
|
|
+ // no results were found directly in scope of `B`.
|
|
|
+ var i: B.Inner;
|
|
|
+ // ❌ Error: name `Inner` is poisoned due to prior lookup in this scope.
|
|
|
+ class Inner {}
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Uses requiring a complete type
|
|
|
+
|
|
|
+While this proposal permits qualified name lookup into types that are
|
|
|
+incomplete, many uses of the names found by such lookups will still require
|
|
|
+completeness. For example:
|
|
|
+
|
|
|
+- Instance binding for a class instance member requires a complete type.
|
|
|
+
|
|
|
+ ```carbon
|
|
|
+ class X[T:! type](v: T) {}
|
|
|
+
|
|
|
+ class A {
|
|
|
+ var n: i32;
|
|
|
+ fn F() -> A;
|
|
|
+ // ❌ Error: `A` is not complete.
|
|
|
+ var m: X(fn (A a) => F().n);
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+- Some uses of interfaces require the interface to be complete, although
|
|
|
+ determining which uses require this is outside the scope of this proposal.
|
|
|
+
|
|
|
+### Lookups into an incomplete generic
|
|
|
+
|
|
|
+It is possible to perform qualified name lookup into a generic type before the
|
|
|
+type is complete.
|
|
|
+
|
|
|
+```carbon
|
|
|
+class X[T:! type](x: T) {}
|
|
|
+
|
|
|
+class A(T:! type) {
|
|
|
+ fn F() -> A(T);
|
|
|
+ // OK, argument to `X` is the `F` function in `A(i32)`.
|
|
|
+ var v: X(A(i32).F);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+This may require extra work to handle in the toolchain. In particular, we
|
|
|
+currently cannot form a specific for the class definition until the class is
|
|
|
+complete. We have at least two viable implementation strategies to handle this:
|
|
|
+
|
|
|
+- Ensure all declarations that can be nested within a type have their own
|
|
|
+ corresponding generic. This is already the case for most such declarations,
|
|
|
+ with field declarations and alias declarations being notable exceptions.
|
|
|
+- Support evaluating an incomplete eval block for a generic, and finish
|
|
|
+ evaluating each incomplete eval block at the end of the generic definition.
|
|
|
+
|
|
|
+## Rationale
|
|
|
+
|
|
|
+- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
|
|
|
+ - This change removes an ergonomic hurdle and an inconsistency in the
|
|
|
+ language rules.
|
|
|
+- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code)
|
|
|
+ - This rule is more closely aligned with the C++ rule for lookup of class
|
|
|
+ members within the class definition, which allows members to be named
|
|
|
+ before the class is complete.
|
|
|
+
|
|
|
+## Alternatives considered
|
|
|
+
|
|
|
+### Require completeness for qualified name lookup
|
|
|
+
|
|
|
+The status quo ante is to disallow qualified access into types until they are
|
|
|
+complete. The rationale for rejecting that alternative is described in this
|
|
|
+proposal.
|
|
|
+
|
|
|
+### Do not require a definition for name lookup
|
|
|
+
|
|
|
+We could permit qualified and unqualified name lookup into types that are merely
|
|
|
+declared and not yet defined. Such a lookup would find nothing. This allowance
|
|
|
+could be used in a case where a type's scope can be extended before the type is
|
|
|
+defined, which is itself currently not permitted. For example:
|
|
|
+
|
|
|
+```carbon
|
|
|
+interface I;
|
|
|
+interface J {
|
|
|
+ let T:! type;
|
|
|
+}
|
|
|
+
|
|
|
+interface K {
|
|
|
+ extend I;
|
|
|
+ extend J;
|
|
|
+ let Unqual:! T;
|
|
|
+ let Qual:! K.T;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+We could choose to permit this, and make the lookups for the name `T` in the
|
|
|
+scope of `I` find nothing and poison the name `T` in `I`, so that the name can
|
|
|
+be resolved immediately to `J.T`. If `I` were to later introduce a name `T`,
|
|
|
+that would result in an error.
|
|
|
+
|
|
|
+However, this seems like it may be a step too far, and isn't justified by the
|
|
|
+motivations for this change. From an implementation standpoint, it would also
|
|
|
+require tracking a list of poisoned names in a type that doesn't even have a
|
|
|
+scope yet, although that is likely straightforward to handle.
|