dart-lang / language

Design of the Dart language
Other
2.65k stars 202 forks source link

It is not obvious whether special types can be the `on` type for an extension #716

Closed eernstg closed 4 years ago

eernstg commented 4 years ago

The feature specification of static extension methods mentions that

the type dynamic is considered as having all member names, and an expression of type Never or void cannot occur as the target of a member invocation, so none of these can ever have applicable extensions

which prevents e.m from invoking an extension member when e has one of these special types.

But I do not see any rules preventing the special types as the on type of an extension:

extension E on T { // Where `T` is `dynamic`, `Never`, `void`, ...
  ...
}

I think it is not necessary to consider other types (FutureOr comes to mind, of course), because they are allowed, and it does make sense.

But Never could be made an error. It is impossible to invoke an extension method on a receiver of type Never, because it is an error to access any member in any way on such a receiver. So that's not likely to be useful, and an on type of Never essentially enforces that the receiver type is Never (the only other possibilities are X extends Never and similar corner cases).

The type void could be used as an on type of an extension, and it could make sense: If the extension is designed to have side-effects on global state only, it would not need to access this, and it might be useful to use the on type void to document that fact.

Similarly, the type dynamic could be used as an on type in order to use this dynamically in the extension members.

So we may or may not wish to allow void and dynamic as on types, but we probably don't want to allow Never, given that all the non-static members are dead code. Note that #455 contains discussions about Never in many other contexts than extensions, so we should have this connection in mind in order to keep the rules consistent.

@lrhn, @munificent, @leafpetersen, WDYT?

lrhn commented 4 years ago

I have no problem allowing Never. I'd want the analyzer to give a warning because it is, as you say, useless, but it's a complication for the language to disallow it.

If you end up writing extension E on Never, you'll probably realize your issue when you try to test it. If not, I'll have to assume that it's deliberate (not that I can imagine the reason, but that's rather the point: The language should not be restricted merely because I can't imagine a use).

Any code using the extension, E(neverAnything()).foo() will have the foo call being dead code. If we warn about dead code, then that's covered.

Then we avoid getting into weird discussion about whether extension F<T> on T { ... foo...} is allowed when you can write F(throw 0).foo. There is nothing inherently wrong with extension E on Never {} which isn't wrong with class C { Never self; C(this.self) } too. That class is uninstantiable, but we won't disallow it. I see extensions on Never the same way. It's fairly easy, almost unavoidable, to catch the problem if it happens by accident.

leafpetersen commented 4 years ago

I'm also not especially motivated to forbid this. extension E on Never { // static methods } actually seems like possibly the best workaround for the lack of namespaces in Dart. More generally, would we forbid extension E<T extends Never> on T? etc.

eernstg commented 4 years ago

OK, what I hear is broad support for allowing all of them, and then we can address "you probably didn't mean this" as needed, separately, using dead code detection and such.

About the philosophers, dining or not, we have lots of ways to match the same types:

// ... some examples already given, but any top type will do:
extension on FutureOr<FutureOr<Object?>> {}
// ... and any type variable bounded by a top type:
extension E<X extends void?> on X {}
// ... and we can put these types into other types to get ambiguity there:
extension on List<dynamic> {}
extension on List<void?> {}
// ... etc.

We have a lot of machinery which is specifically dealing with ambiguities arising from having several matching extensions for the same call site. We discussed in great detail how to disambiguate in these situations, so we shouldn't need to reconsider.

eernstg commented 4 years ago

life would be boring

Can't have that, we all know it's good to live in interesting times. ;-)

eernstg commented 4 years ago

Closing: I'll conclude that we allow all valid Dart types to be an on type of an extension. This matches the following, which is already in the extension-method feature specification:

The type can be any valid Dart type, including a single type variable.