dart-lang / language

Design of the Dart language
Other
2.64k stars 198 forks source link

No implicit `dynamic` #3842

Open lrhn opened 2 months ago

lrhn commented 2 months ago

This is a collection of features, which together should reduce the risk of dynamic being introduced into a type, where the author didn't explicitly write dynamic.

Changes

Breakage mitigation

These changes are all potentially (and likely actually) breaking.

Mitigation is automatic migration, making the dynamic explicit.

These changes should minimize the language introducing dynamic without the author asking for it, without interfering from any explicitly written dynamic. Combining dynamic with a non-dynamic type in an upper bound never gives dynamic.

Interaction with pre-feature code

The feature is language versioned. Changes affecting code that uses declarations, not the declarations themselves, are unaffected until the code upgrades its language version. At that point it needs to migrate to preserve the current behavior. Code that changes the type of declarations changes when the declarations upgrade their language version, They too need to migrate to preserve the current behavior.

There is no change which combines behavior from two language versions (like null safety did with int? and int* types). The dynamic is either inferred as normal in pre-feature code, or it changes meaning in post-feature code unless migrated.

lrhn commented 2 months ago

As an alternative, we could make an omitted return type mean void. That might actually be useful, and meaningful: A function with no return type does not return anything. We already allow (and recommend) omitting void from setters.

julemand101 commented 2 months ago

As an alternative, we could make an omitted return type mean void. That might actually be useful, and meaningful: A function with no return type does not return anything. We already allow (and recommend) omitting void from setters.

I am in favor of this. That would also give users an error/warning if they try to return a value from this implicit void-returning method which would nudge them into specify the actual type of value to be returned.

tatumizer commented 2 months ago

As an alternative, we could make an omitted return type mean void

Compare two declarations:

var foo = () => 5; 
bar() => 5;

The compiler infers the return type of foo as int Function() foo, but the return type of bar as dynamic bar() It means that whenever we pass a closure as a parameter of, say, map method, it will infer a correct type, but if we pass a named function instead, the return type will become void, causing an untold breakage, right?

jakemac53 commented 2 months ago

It means that whenever we pass a closure as a parameter of, say, map method, it will infer a correct type, but if we pass a named function instead, the return type will become void, causing an untold breakage, right?

See Mitigation is automatic migration, making the dynamic explicit. - assuming this automatic migration is implemented and used, it will insert an explicit dynamic in all these places, which should prevent this kind of breakage during transition. It should be rare anyways though, all named functions should have return types.

tatumizer commented 2 months ago

Will any breakage be caused if, instead of inserting explicit dynamic, the migration tool inserts int in this case?

bar() => 5;

If there's no harm, then maybe this example can be generalized to all "safe" cases (the definition of "safe" - TBD). The idea is this: in the rare (?) cases when the tool substitutes an explicit "dynamic", the user will want to change it afterward anyway to a concrete type, and will be wondering whether this change will be breaking or not. If it's known in advance that such a change won't be breaking, why not fix it automatically?

jakemac53 commented 2 months ago

Will any breakage be caused if, instead of inserting explicit dynamic, the migration tool inserts int in this case?

Yes, there could be breakage in valid code today, in particular where type inference is concerned:

var x = bar();
x = 'hello'; // Ok when dynamic was inferred, not ok when int was inferred.

var y = [bar()]; // List<dynamic> before, List<int> after
y.add('hello'); // Error with List<int> 
mmcdon20 commented 2 months ago

Are there any situations where someone would actually want an implicit Object?? If you are going to make a breaking change like this, then why not just make it an error to omit the type in these situations?

mmcdon20 commented 2 months ago

As an alternative, we could make an omitted return type mean void. That might actually be useful, and meaningful: A function with no return type does not return anything. We already allow (and recommend) omitting void from setters.

Another alternative would be to type inference an omitted return type. Anonymous functions already work this way, and some languages (typescript for example) treat an omitted return type this way.