dart-lang / dart-lang/language
[extension-types] Allow redirecting factories to redirect to the representation type?
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
We could consider the following small relaxation of the rules about redirecting factory constructors on extension types:
> Assume that `E` is an extension type with instantiated representation type `R`. Assume that the declaration of `E` declares a redirecting factory constructor _k_. It is a compile-time error unless the redirectee of _k_ has a return type which is a subtype of `E` or a subtype of `R`.
This is a relaxation compared to the current rules, and to the rule about other redirecting factory constructors, e.g., the ones that occur in a class or an enum declaration: they just have the part that corresponds to "which is a subtype of `E`". In other words, the proposal is that the redirectee can yield the extension type or the representation type, both are OK.
This matters because it allows us to provide restricted access to a given class (here: `_Private`) using an extension type as a "publice face" (`Public`), and we can also provide access to `const` constructors of `_Private`.
As is common with constant expressions, there is no way to achieve the same effect without this relaxation of the rules.
For example, the following would be allowed (but is currently a compile-time error):
```dart
class _Private {
final num _i;
const _Private(this._i);
}
extension type const Public._(_Private _it) {
const factory Public(int i) = _Private;
int get i => _it._i;
}
```
This is type safe because any instance of type `_Private` is a type correct representation object for the extension type `Public`. Apart from the `const` modifier, it's just like the following, which is supported today:
```dart
extension type const Public._(_Private _it) {
factory Public(int i) => Public._(_Private(i)); // or `_Private(i) as Public`.
}
```
You could say that this proposal allows `const factory Public(int i) = _Private;` because that constructor invocation (or that cast) doesn't "do anything" anyway (in particular, the cast will never fail at run time). It is conceptually benign because it's just a constant version of something which is already possible using well-known mechanisms in a non-redirecting factory constructor.
The motivation for having this feature is basically that it allows an extension type to be a more complete "public face" of one or more underlying classes, which are then allowed to be of no relevance for client developers (they don't need to know about them), or even inaccessible (private). Currently, constant expressions may be the only reason why clients would have to know about the underlying representation type.
The ability to have an extension type as a public face on top of a given representation type can have many different effects. One example is that the `_Private`/`Public` design above allows us to enforce that every invocation (outside the declaring library) of members on `_Private` is statically checked: There is no other way (outside the declaring library) to invoke the getter `_Private._i` than via an expression of type `Public`, using the name `i`. Note that regular instance members do not have this property: If you can call a member `m` at all (that is, `m` is a public name, or it is declared in the current library) then you can also call it dynamically.
Extension members (`extension`, not `extension type`) can do a similar thing, but they are less convenient for this purpose because they need to be imported directly in order to be available, and developers would not be able to denote the type (they can't do `final _Private myVariable;` in a different library, whereas `final Public myVariable;` works just fine). If you're trying to get by using the extension rather than the extension type, you'll need to use the interface of `_Private`. But this interface can only be used directly on an expression of type `_Private`, or on a variable whose type is inferred as `_Private`, and that's a very limited kind of usage. Basically, this doesn't work.
Asking around (thanks, @johnniwinther and @chloestefantsova!) the implementation effort seems to be non-trivial, but not very large, at least in the CFE. In particular, there may be a need to do some work in the area of constructor tear-off identity.
Contributor guide
Assessment
This issue has not been assessed yet.