Freezed: Support recursive classes

Created on 12 Feb 2020  路  14Comments  路  Source: rrousselGit/freezed

Edited to include a better description of the issue (@rrousselGit)

Consider:

@immutable
abstract class Foo with _$Foo {
  factory Foo(RedirectedName value) = RedirectedName; 
}

This is a recursive class, as the default constructor somehow refers to itself.
But since RedirectedName has yet to be generated, Freezed does not understand what is happening and parse RedirectedName as dynamic instead.


Original message:

Hello, Thanks for your great package.
I created a simple class with 2 constructor with the following code but when I want to use price property in PriceWithQuantity class it just return dynamic instated of Price class

@immutable
abstract class PriceExample with _$PriceExample {
  const factory PriceExample.price(
    int id,
    int price,
  ) = Price;

  const factory PriceExample.withQuantity(
    Price price,
    int quantity,
  ) = PriceWithQuantity;
}

enhancement

Most helpful comment

Basic support has landed on 0.9.2

Tell me if this is not enough for you 馃槃

All 14 comments

It doesn't return dynamic, it's simply not there.

Both PriceExample.price and PriceExample.withQuantity define a price parameter, but they have a different type.

As such you have to use when/map/is to determine if it's a .price or .withQuantity, to then be able to access price.

also does not work for when or map because generated class return dynamic.
here is generated class

abstract class PriceWithQuantity implements PriceExample {
  const factory PriceWithQuantity(dynamic price, int quantity) =
      _$PriceWithQuantity;

  dynamic get price; /// this should be [Price] class not dynamic
  int get quantity;

  PriceWithQuantity copyWith({dynamic price, int quantity});
}

And if I know I am using PriceWithQuantity class directly it should return instance of Price with quantity property. for example passing instance of PriceWithQuantity

void addQuantity(PriceWithQuantity quantity){
//code
}

Oh, I've just realized what you did.

That's an interesting case. It's likely because Price hasn't been generated yet.

I'm not too sure how to support that.

It may be more logical to write:

@immutable
abstract class Price with _$Price {
  const factory Price.price(
    int id,
    int price,
  ) = _Price;
}

@immutable
abstract class PriceExample with _$PriceExample {
  const factory PriceExample.withQuantity(
    Price price,
    int quantity,
  ) = PriceWithQuantity;
}

Or alternatively, you do:

@immutable
abstract class PriceExample with _$PriceExample {
  const factory PriceExample(int id, int price) = Price;

  const factory PriceExample.withQuantity(
    int id,
    int price,
    int quantity,
  ) = PriceWithQuantity;
}

Thanks for your suggestion, its just a simple example more properties it will cause a lot of code duplication, but separating class or casting manually for now it just work.

Again Thanks

No problem 馃槃

I'll leave this open as it should be able to properly fix this since Freezed should be able to infer that the type used is a union.\
It's not a priority though, as it could get fairly complex with generics because it involves manually parsing the type.

Remi I think I have a simliar issue but I'm not sure your previous work around is going to work fo rme. I'm trying to model data structure simliar to std Flutter widgets so I have something like this:

import 'package:flutter/foundation.dart';

part 'widget_type.freezed.dart';

@immutable
abstract class WidgetType with _$WidgetType {
  const factory WidgetType.page({Body body, Header header}) = Page;
  const factory WidgetType.body() = Body;
  const factory WidgetType.header({String title}) = Header;
}

It mostly works but in the generated code the Page class uses dynamic instead of the actual types:

abstract class Page implements WidgetType {
  const factory Page({dynamic body, dynamic header}) = _$Page;

  dynamic get body;
  dynamic get header;

  Page copyWith({dynamic body, dynamic header});
}

I havent had a chance to look at how the code gen is done, but perhaps it would be possible to do a second pass before writing out the generated src to "patch in" the types for classes that take instances of thier own Union type as parameters?

I havent had a chance to look at how the code gen is done, but perhaps it would be possible to do a second pass before writing out the generated src to "patch in" the types for classes that take instances of thier own Union type as parameters?

Yes. The problem is that this is pretty difficult to do reliably.

The analyzer doesn't expose the source of the unparsed type, so we have to parse it manually. It can get very messy easily with generics and decorators and co

For now the easiest solution is to extract your Body/Header as independent classes:

@immutable
abstract class Body with _$Body {
  const factory Body() = _Body;
}

@immutable
abstract class Header with _$Header {
  const factory Header() = _Header;
}

@immutable
abstract class WidgetType with _$WidgetType {
  const factory WidgetType.page({Body body, Header header}) = Page;
}

@rrousselGit it also return dynamic even using separate class, does I'm doing something wrong?

@immutable
abstract class First with _$First{
  const factory First.a(String data) = A;
  const factory First.b(int data) = B;
}

@immutable
abstract class Second with _$Second{
  const factory Second(A a) = SecondConst;
}

Try to avoid using what's on the right side of the = of a constructor.

I'll think about a fix, but in the mean time that's your best bet

@rrousselGit This doesn't work with the current code gen, but perhaps something like this could be made to work just using the existing analyzer approach?

import 'package:flutter/foundation.dart';

part 'widget_type.freezed.dart';

@immutable
abstract class WidgetType with _$WidgetType {
  const factory WidgetType() = _WidgetType;
  const factory WidgetType.page() = _Page;
}

@immutable
abstract class Page with _$Page implements WidgetType {
  const factory Page({Body body, Header header}) = _Page;
}

so its a bit more work for Freezed users but then we are telling Freezed what the specific subclass for each of the Unions factory constructors.

I don't like it.
I do have some ideas on how to solve this issue already anyway. I just need to work on it.

I'll first finish the deep equality comparison and the copyAs, then I'll work on this.

Basic support has landed on 0.9.2

Tell me if this is not enough for you 馃槃

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mohammadne picture mohammadne  路  3Comments

narcodico picture narcodico  路  6Comments

rakakhrl picture rakakhrl  路  6Comments

pblinux picture pblinux  路  4Comments

agordeev picture agordeev  路  4Comments