rrousselGit / freezed

Code generation for immutable classes that has a simple syntax/API without compromising on the features.
https://pub.dev/packages/freezed
1.91k stars 234 forks source link

Support Freezed classes extending another Freezed class. #907

Open rrousselGit opened 1 year ago

rrousselGit commented 1 year ago

Cases to handle:

@freezed
sealed class A with _$A {
  factory A.one() = A1;
}

@freezed
class B extends A with _$B {
  factory B.one() = B1;
  factory B.two() = B2;
}

void main() {
  B b;
  switch (b){
    case B1(): print('b1');
    case B2(): print('b2');
  }

  A a;
  switch (a){
    case A1(): print('a1')
    case B1(): print('b1');
    case B2(): print('b2');
  }
}

with variants such as

@freezed
class B extends A1 with _$B {...}
rrousselGit commented 1 year ago

Probably would require disabling when generation. But we have pattern matching now :shrug;

rrousselGit commented 1 year ago

Currently we can do:

sealed class B implements A {
  int get moreCommonGround;
} 

@freezed
sealed class A with _$A {
  @Implements<B>();
  factory A.c(int commonGround, int moreCommonGround) = C;
  @Implements<B>();
  factory A.d(int commonGround, int moreCommonGround) = D;
  factory A.z(int commonGround) = Z;
}

The main genefit of this proposal is:

TimWhiting commented 1 year ago

Haha, I've actually been doing the implements workaround already for a few days with sealed classes. Would love to see this feature.

jacobokoenig commented 9 months ago

@TimWhiting can you share your workaround? Trying to extend a class that uses freezed and I would hate to have to remove the freezed implementation

TimWhiting commented 9 months ago

Currently we can do:

sealed class B implements A {
  int get moreCommonGround;
} 

@freezed
sealed class A with _$A {
  @Implements<B>();
  factory A.c(int commonGround, int moreCommonGround) = C;
  @Implements<B>();
  factory A.d(int commonGround, int moreCommonGround) = D;
  factory A.z(int commonGround) = Z;
}

The main genefit of this proposal is:

  • B could have a fromJson directly
  • B would have a custom copyWith with its shared properties.

This is the workaround. You can't extend the freezed class. You have to use Implements annotation to share common ground between a subset of the cases.

Then just use pattern matching instead of when / map. If you want shared implementation details, then you can use the With annotation to share implementations based on the interface you Implements using a mixin on the interface. Or you can implement extension methods based on the interface you Implements. If you want exhaustive pattern matching on a subset of the constructors, then make the interface you implement an sealed class. Make all the definitions in the interface just getters or unimplemented methods since you won't actually inherit any implementations using Implements.

Here is a file that uses this approach. https://github.com/byu-static-analysis-lab/syntax/blob/main/lib/src/scheme/scheme_syntax.dart

Maybe you cna share your example to see if we have any more hints. Maybe inline the constructors for the freezed class you are trying to extend in the current freezed class you are creating and use this methodology to create shared implementation and relationships that was in your class you were trying to extend.