Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type aliases: Typedefs for non-function types #65

Closed
mit-mit opened this issue Oct 31, 2018 · 82 comments
Closed

Type aliases: Typedefs for non-function types #65

mit-mit opened this issue Oct 31, 2018 · 82 comments
Assignees
Labels
feature Proposed language feature that solves one or more problems

Comments

@mit-mit
Copy link
Member

mit-mit commented Oct 31, 2018

Realizes #66, Feature specification

Sample typedef & usage:

typedef IntList = List<int>;
IntList il = [1,2,3];

Related issues: dart-lang/sdk#2626

@mit-mit mit-mit added feature Proposed language feature that solves one or more problems state-active labels Oct 31, 2018
@eernstg
Copy link
Member

eernstg commented Oct 31, 2018

Specification CL at https://dart-review.googlesource.com/c/sdk/+/81414.

@eernstg
Copy link
Member

eernstg commented Nov 1, 2018

Feature specification proposal in this PR.

@eernstg
Copy link
Member

eernstg commented Nov 6, 2018

Said feature specification landed as efdc2fa.

@eernstg
Copy link
Member

eernstg commented Nov 8, 2018

PR created for the feature specification: One rule needs to be discussed, and the right thing to do is to say explicitly that no conclusion has been reached yet, with respect to that particular rule.

@leafpetersen
Copy link
Member

I opened an issue for discussion of the question of using typedefs where classes are expected here.

@eernstg
Copy link
Member

eernstg commented Nov 9, 2018

Said PR now landed.

@leafpetersen
Copy link
Member

I think we need to update the spec to reflect the consensus on static method access.

@eernstg
Copy link
Member

eernstg commented Nov 30, 2018

Landed #116 for that.

@mit-mit mit-mit moved this from Being spec'ed to Ready for implementation in Language funnel Nov 30, 2018
@georgelesica-wf
Copy link

georgelesica-wf commented Jul 8, 2019

I perused the spec, but I didn't see an obvious answer. How will the analyzer handle this?

typedef IntList = List<int>;
IntList il = [1,2,3];
List<int> li = il; // potentially problematic

Presumably that would be either a warning or an error, correct? What about this one (building on the code above)?

void foo(IntList a) {}
List<int> b = [1,2,3];
foo(b); // potentially problematic

In this case it isn't clear that b is intended to be an actual IntList, so allowing the call to foo might introduce a bug. On the other hand, I suspect that quite a few people would want to do this without an explicit cast for the sake of convenience.

@eernstg
Copy link
Member

eernstg commented Jul 8, 2019

List<int> and IntList is the same type, just different spelling, so there should not be a problem with this example: Inference should make [1,2,3] mean <int>[1,2,3] and the initialization of li is fine. Same situation with foo(b).

@georgelesica-wf
Copy link

But someone might change the definition of IntList, and if the piece of data in question was never really intended to follow IntList in the first place, a call like the second one in my comment shouldn't be allowed in that case. In other words, everything might type check accidentally.

For example, the second call to foo here won't compile because I've declared b to be an int, but not necessarily a MyInt.

package main

type MyInt int

func foo(a MyInt) {
    println(a)
}

func main() {
    var a MyInt = 0
    foo(a)
    var b int = 0
    foo(b)
}

@rrousselGit
Copy link

That's not the same thing.
This feature doesn't create a new type, it just makes an alias.

typedef Foo = int;

void main() {
  print(int == Foo); // true
}

@georgelesica-wf
Copy link

georgelesica-wf commented Jul 8, 2019

OK, that's really what I was curious about, I suppose. Thanks for clarifying!

@rrousselGit
Copy link

Would this include:

typedef Test<T> = T Function();

typedef Test2 = Test<int>;

?

Currently, this code does not compile, for no obvious reason. This is very limiting if we have anything more complex than T Function().

@eernstg
Copy link
Member

eernstg commented Nov 5, 2019

The 'typedefs for non-function types' feature has not yet been implemented. It will happen, but the efforts to support other features (especially extension methods and non-nullable types) have left no resources available for this feature, so it's been postponed for a while.

This means that we only have the kind of type alias that does not support non-function types: The grammar rule requires a <functionType> on the right hand side of =.

That makes typedef Test2 = Test<int> a syntax error (actually, no tool is required to tell which compile-time errors are syntax errors and which ones are "other" compile-time errors, but it must be a compile-time error of some kind).

When this feature is implemented it will certainly be possible to have declarations like typedef Test2 = Test<int>;.

In any case, there should not be any particular limits on the complexity of the types that you define a given type alias to denote (neither with the current type alias feature, nor with the generalization that supports = <type> rather than = <functionType>).

@shinayser
Copy link

One question about this proposal:
If I define an extension function on a type alias, it will be available to the 'aliased' type? Like:

typedef IntList = List<int>;

extension on IntList {
   doSometing();
}

var listOfItens = [1,2,3];

listOfItens.doSomething(); <-- It will be available?

@rrousselGit
Copy link

rrousselGit commented Nov 7, 2019

That's how it works already with extensions on functions, so likely.

typedef VoidCallback = void Function();

extension on VoidCallback {
  void foo() {}
}

void main() {
  void Function() test = () {};

  test.foo();
}

@shinayser
Copy link

shinayser commented Nov 7, 2019

That's how it works already with extensions on functions, so likely.

typedef VoidCallback = void Function();

extension on VoidCallback {
  void foo() {}
}

void main() {
  void Function() test = () {};

  test.foo();
}

Wow thats bad to hear 😢
If it was not available, we could create fake class inheritance, giving us some interesting possibilities. =/

@rrousselGit
Copy link

But that's not a typedef anymore. That's a new type
Your request may be part of #546.

@themisir
Copy link

typedef IntList = List<int>;
IntList il = [1,2,3];

To be honest, I think that is the worst possible example 😂

I remember when I write pascal I had to use StringList because it doesn't supported templates (aka: generics).

@themisir
Copy link

themisir commented Mar 12, 2021

One of the more popular examples would be JSON:

typedef Json = Map<String, dynamic>;

class User {
  final String name;
  final int age;

  User.fromJson(Json json) : 
    name = json ["name"],
    age = json ["age"];
  
  Json get json => {
    "name": name, 
    "age": age,
  };
}

Since json objects can be also arrays and dart doesn't supports union types like List<dynamic> | Map<String, dynamic> why we're not using dynamic for storing json data? Don't take it serious, i'm myself using Map<String, dynamic> and using DTOs on higher level layers.

@Levi-Lesches
Copy link

You're right, in other instances I do use Lists. But the way I tend to structure my data, everything is a class in a Map, or a list of such maps. So when I have a list, it usually ends up being passed into User.fromJson one by one, hence the Maps everywhere. Personally, I feel that types are not a big issue when it comes to JSON, since it's either Map<String, dynamic> or List<dynamic>. Once you determine if it's a list or a map, you're pretty much done.

Also, am I the only one who does

Json get json => { };

instead of

Json toJson() => { };

It feels like every example I see has the latter, but I find getters to be so much cleaner.

@jodinathan
Copy link

You're right, in other instances I do use Lists. But the way I tend to structure my data, everything is a class in a Map, or a list of such maps. So when I have a list, it usually ends up being passed into User.fromJson one by one, hence the Maps everywhere. Personally, I feel that types are not a big issue when it comes to JSON, since it's either Map<String, dynamic> or List<dynamic>. Once you determine if it's a list or a map, you're pretty much done.

Also, am I the only one who does

Json get json => { };

instead of

Json toJson() => { };

It feels like every example I see has the latter, but I find getters to be so much cleaner.

I guess it is common to set a local var to function calls while a getter is often called multiple times.

@themisir
Copy link

themisir commented Mar 12, 2021

You can also write something like that if you want to sacrifice code cleanness for 1ms performance improvement:

Json _json;
Json get json => _json ??= <String, dynamic>{
  ...
};

It will cache json value in _json field which will not require rebuilding Map on each usage.

@eernstg
Copy link
Member

eernstg commented Mar 12, 2021

This feature is really near to completion now. A useful example could be a type alias that encapsulates a certain regularity in the use of an existing type:

typedef MapToList<X> = Map<X, List<X>>;

void main() {
  MapToList<int> m = {};
  m[7] = [7];
  m[8] = [2, 2, 2];
  for (var x in m.keys) {
    print('$x --> ${m[x]}');
  }
}

The point is that MapToList<X> allows us to specify int just once, and then the standard form Map<int, List<int>> is implied, and that will compute the second type argument to the map literal {} implicitly.

@Levi-Lesches
Copy link

Thanks for all your hard work on this feature! Dart is special in that it pays so much attention to the little things.

@bernaferrari
Copy link

A wild commit appeared yesterday! It seems this will be released with Dart 2.13!

https://dart-review.googlesource.com/c/sdk/+/192948

@Levi-Lesches
Copy link

Thanks for that!

There's a great example in the CHANGELOG that is probably worth repeating -- deprecating classes.

Let's say you have this:

class BadName { /* some methods */ }
class OtherLibraries extends BadName { }

So you want to rename BadName, but that's a breaking change since other people depend on them. Well, you can safely deprecate it by doing this:

class BetterName { /* some methods */ }  // changed the original

@Deprecated("Use BetterName instead")
typedef BadName = BetterName;

Now, all classes that extend BadName will cotinue to work, but will show a warning. When it's time for the breaking change, simply remove the typedef and keep everything else the same.

@leafpetersen
Copy link
Member

A wild commit appeared yesterday! It seems this will be released with Dart 2.13!

https://dart-review.googlesource.com/c/sdk/+/192948

Heh - can't keep anything from you guys. :) Just to manage expectations a bit... I haven't landed that patch yet, and there's still a non-zero (but hopefully small) chance that we won't make the branch cut date for 2.13.

@creativecreatorormaybenot
Copy link
Contributor

creativecreatorormaybenot commented Mar 30, 2021

It does not change a lot when it will be released in the near future in my mind @leafpetersen :)

Considering that the feature has been finished in most places (according to dart-lang/sdk#44951) and the fact that we should be aware of the standard release cycle, we should not have any different expectations in the first place ;)

slimlime pushed a commit to portal-webstore/portal-webstore.github.io that referenced this issue Apr 14, 2021
Dart more flexible typedefs may be coming soon in Dart 2.13
dart-lang/language#65
@shinayser
Copy link

Why this issue stills open? 😁😁😁

@eernstg
Copy link
Member

eernstg commented May 19, 2021

You're right, done!

@eernstg eernstg closed this as completed May 19, 2021
@mit-mit mit-mit moved this from Being implemented to Done in Language funnel May 19, 2021
@Levi-Lesches
Copy link

Levi-Lesches commented Jul 22, 2021

Now that I actually want to use this feature (with JSON types, surprise surprise), I came across a problem: Where do I actually put the typedef?

In my Flutter app, I have this file structure:

|- lib
   |- data.dart -- exports everything in src/data
   |- src 
      |- data
         |- a.dart
         |- b.dart
         |- c.dart

If I want classes A, B, and C, each in their respective files, where should I put the Json alias? Seems like a lot of overhead to add a json_alias.dart file just for that. If I put it in each of the data files, then there will be conflicts. And I don't want to put it in any one data file, since Json isn't intrinsic to any specific class. If I put it in data.dart, then all importers can see it, but none of the data files can use it in their definitions!

@eernstg, thoughts? Are there any tests that happen to import aliases that I can pull inspiration from?

@munificent
Copy link
Member

Seems like a lot of overhead to add a json_alias.dart file just for that.

I would probably do this.

@Levi-Lesches
Copy link

Okay, thanks for the recommendation, I'll do that.

@Zabadam
Copy link

Zabadam commented Jul 24, 2021

@Levi-Lesches
Now that I actually want to use this feature (with JSON types, surprise surprise), I came across a problem: Where do I actually put the typedef?

If you will be using more typedefs, a suggestion would be an encompassing root-level typedefs.dart; it doesn't need to be exported.

I have seen a lot of packages that would, in your scenario, have a src/data/common.dart solution, importing common into each a, b, and c.

@lalomartins
Copy link

Folks, this is all very interesting but Github bug comments are not the right space for it.

@fanthus
Copy link

fanthus commented Aug 3, 2021

I tried this feature in IntelliJ dart plugin , like it still not support.

@eernstg
Copy link
Member

eernstg commented Aug 3, 2021

@fanthus, if you wish to report an issue with the IntelliJ plugin and non-function type aliases, please create an issue with 'area-intellij' and give more details.

@esenmx
Copy link

esenmx commented Aug 28, 2021

Can it be possible to get the name of typedef instead the underlying runtimeType in future? Since it's just an alias, I think it might be feasible with an extra field for variables with typedefs.

typedef Meaning = int;

final Meaning life = 42;

void main() {
  print(life.runtimeType); // prints 'int'
  print(life.typedef); // prints 'Meaning'
}

Why I would need this?
Most of the time, I use runtimeType for labeling things in logs. But for typedefs it's not a case obviously. Example:

typedef SessionID = String;
final provider = Provider<SessionID>();

runtimeType is Provider<String> but there could be way to print Provider<SessionID> which is much more identifiable.
Also it would be very beneficial for writing something like golang style type-switch.

@creativecreatorormaybenot
Copy link
Contributor

creativecreatorormaybenot commented Aug 28, 2021

@esenmx The way you describe it, it is a new type rather than an alias. I guess the problem is that extends String does not work, but it is not an alias if you can switch on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
Status: Done
Development

No branches or pull requests