Explorar o código

Generic impls access (details 4) (#931)

Implementations of interfaces are as public as the names used in their signature. No access control modifiers are allowed on `impl` declarations.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
josh11b %!s(int64=4) %!d(string=hai) anos
pai
achega
be8d0a993b
Modificáronse 2 ficheiros con 201 adicións e 0 borrados
  1. 58 0
      docs/design/generics/details.md
  2. 143 0
      proposals/p0931.md

+ 58 - 0
docs/design/generics/details.md

@@ -17,6 +17,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
     -   [Implementing multiple interfaces](#implementing-multiple-interfaces)
     -   [External impl](#external-impl)
     -   [Qualified member names](#qualified-member-names)
+    -   [Access](#access)
 -   [Generics](#generics)
     -   [Implementation model](#implementation-model)
 -   [Interfaces recap](#interfaces-recap)
@@ -35,6 +36,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
     -   [Extending adapter](#extending-adapter)
     -   [Use case: Using independent libraries together](#use-case-using-independent-libraries-together)
     -   [Use case: Defining an impl for use by other types](#use-case-defining-an-impl-for-use-by-other-types)
+    -   [Use case: Private impl](#use-case-private-impl)
     -   [Adapter with stricter invariants](#adapter-with-stricter-invariants)
 -   [Associated constants](#associated-constants)
     -   [Associated class functions](#associated-class-functions)
@@ -591,6 +593,24 @@ p.(Plot.Drawable.Draw)();
 C++, adding `ClassName::` in front of a member name to disambiguate, such as
 [names defined in both a parent and child class](https://stackoverflow.com/questions/357307/how-to-call-a-parent-class-function-from-derived-class-function).
 
+### Access
+
+An `impl` must be visible to all code that can see both the type and the
+interface being implemented:
+
+-   If either the type or interface is private to a single file, then since the
+    only way to define the `impl` is to use that private name, the `impl` must
+    be defined private to that file as well.
+-   Otherwise, if the type or interface is private but declared in an API file,
+    then the `impl` must be declared in the same file so the existence of that
+    `impl` is visible to all files in that library.
+-   Otherwise, the `impl` must be defined in the public API file of the library,
+    so it is visible in all places that might use it.
+
+No access control modifiers are allowed on `impl` declarations, an `impl` is
+always visible to the intersection of the visibility of all names used in the
+declaration of the `impl`.
+
 ## Generics
 
 Here is a function that can accept values of any type that has implemented the
@@ -1850,6 +1870,43 @@ class IntWrapper {
 }
 ```
 
+### Use case: Private impl
+
+Adapter types can be used when a library publicly exposes a type, but only wants
+to say that type implements an interface as a private detail internal to the
+implementation of the type. In that case, instead of implementing the interface
+for the public type, the library can create a private adapter for that type and
+implement the interface on that instead. Any member of the class can cast its
+`me` parameter to the adapter type when it wants to make use of the private
+impl.
+
+```
+// Public, in API file
+class Complex64 {
+  // ...
+  fn CloserToOrigin[me: Self](them: Self) -> bool;
+}
+
+// Private
+
+adapter ByReal extends Complex64 {
+  // Complex numbers are not generally comparable,
+  // but this comparison function is useful for some
+  // method implementations.
+  impl as Comparable {
+    fn Less[me: Self](that: Self) -> bool {
+      return me.Real() < that.Real();
+    }
+  }
+}
+
+fn Complex64.CloserToOrigin[me: Self](them: Self) -> bool {
+  var me_mag: ByReal = me * me.Conj() as ByReal;
+  var them_mag: ByReal = them * them.Conj() as ByReal;
+  return me_mag.Less(them_mag);
+}
+```
+
 ### Adapter with stricter invariants
 
 **Future work:** Rust also uses the newtype idiom to create types with
@@ -3591,3 +3648,4 @@ parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`.
 -   [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553)
 -   [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
 -   [#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818)
+-   [#931: Generic impls access (details 4)](https://github.com/carbon-language/carbon-lang/pull/931)

+ 143 - 0
proposals/p0931.md

@@ -0,0 +1,143 @@
+# Generic impls access (details 4)
+
+<!--
+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/931)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
+-   [Alternatives considered](#alternatives-considered)
+    -   [Private impls for public types](#private-impls-for-public-types)
+    -   [Private interfaces in public API files](#private-interfaces-in-public-api-files)
+
+<!-- tocstop -->
+
+## Problem
+
+Should a type be able to declare an `impl` to be private, like it can for its
+data and methods? There are some use cases for this feature, but those use cases
+generally could also be addressed by implementing the interface on a private
+adapter type instead.
+
+## Background
+
+Private impls have been considered but not implemented for Swift in the
+[scoped conformances pitch](https://forums.swift.org/t/scoped-conformances/37159).
+
+## Proposal
+
+This is a proposal to add to
+[this design document on generics details](/docs/design/generics/details.md).
+The additions are:
+
+-   to say impls do not allow access control modifiers and are always as public
+    as the names used in the signature of the impl; and
+-   to document how to use private adapter types instead.
+
+## Rationale based on Carbon's goals
+
+We decided to make generics coherent as part of accepting proposal
+[#24: generics goals](https://github.com/carbon-language/carbon-lang/pull/24).
+This approach is consistent with broader Carbon goals that Carbon have
+[code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write).
+In particular, we favor making code
+[less context sensitive](/docs/project/principles/low_context_sensitivity.md).
+
+## Alternatives considered
+
+### Private impls for public types
+
+We considered supporting private impls for public types. This was motivated by
+using the conversion of a `struct` type to a `class` type to construct class
+values. In the case that the class had private data members, we wanted to
+restrict that conversion so it was only available in the bodies of class members
+or friends of the class. We considered achieving that goal by saying that the
+implementation of the conversion would be private in that case. Allowing impls
+to generally be private, though, led to a number of coherence concerns that the
+same code would behave differently in different files. Our solution was to
+address these conversions using a different approach that only addressed the
+conversion use case:
+
+-   Users could not implement `struct` to `class` conversions themselves.
+-   The compiler would generate `struct` to `class` conversions for constructing
+    class values itself.
+-   Those conversion impls will always be as visible as the class type, and will
+    still be selected using the same rules as other impls.
+-   When one of these compiler-generated conversion impls is selected, the
+    compiler would perform an access control check to see if it was allowed.
+
+We believe that other use cases for restricting access to an impl are better
+accomplished by using a private adapter type than supporting private impls.
+
+### Private interfaces in public API files
+
+A private interface may only be implemented by a single library, which gives the
+library full control. We considered and rejected the idea that developers could
+put that interface declaration in an API file to allow it to be referenced in
+named constraints available to users. All impls for that interface would also
+have to be declared in the API file, unless they were for a private type
+declared in that library.
+
+This would allow you to express things like:
+
+-   A named constraint that is satisfied for any type **not** implementing
+    interface `Foo`:
+
+    ```
+    class TrueType {}
+    class FalseType {}
+    private interface NotFooImpl {
+      let IsFoo:! Type;
+    }
+    impl [T:! Foo] T as NotFooImpl {
+      let IsFoo:! Type = TrueType;
+    }
+    impl [T:! Type] T as NotFooImpl {
+      let IsFoo:! Type = FalseType;
+    }
+    constraint NotFoo {
+      extends NotFooImpl where .IsFoo == FalseType;
+    }
+    ```
+
+-   A named constraint that ensures `CommonType` is implemented symmetrically:
+
+    ```
+    // For users to implement for types
+    interface CommonTypeWith(U:! Type) {
+      let Result:! Type where ...;
+    }
+
+    // internal library detail
+    private interface AutoCommonTypeWith(U:! Type) {
+      let Result:! Type where ...;
+    }
+
+    // To use as the requirement for parameters
+    constraint CommonType(U:! AutoCommonTypeWith(Self)) {
+      extends AutoCommonTypeWith(U) where .Result == U.Result;
+    }
+
+    match_first {
+      impl [T:! Type, U:! ManualCommonTypeWith(T)]
+          T as AutoCommonTypeWith(U) {
+        let Result:! auto = U.Result;
+      }
+      impl [T:! Type, U:! ManualCommonTypeWith(T)]
+          U as AutoCommonTypeWith(T) {
+        let Result:! auto = U.Result;
+      }
+    }
+    ```
+
+This feature is something we might consider adding in the future.