Skip to content

Commit

Permalink
Re-introduced With.fromString/Implements.fromString
Browse files Browse the repository at this point in the history
  • Loading branch information
rorystephenson committed Oct 14, 2022
1 parent 233519c commit b2ca8a5
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 37 deletions.
29 changes: 16 additions & 13 deletions packages/freezed/README.md
Expand Up @@ -814,10 +814,10 @@ The reasoning is that they are not "exhaustive". See https://www.fullstory.com/b

### Mixins and Interfaces for individual classes for union types

When you have multiple types in the same class you might want to make
one of those types to implement an interface or mixin a class. You can do
that using the `@Implements` decorator or `@With` respectively. In this
case `City` is implementing with `GeographicArea`.
When you have multiple types in the same class you might want one of those
types to implement an interface or mixin a class. You can do that using the
`@Implements` or `@With` decorators respectively. In the following example
`City` implements `GeographicArea`.

```dart
abstract class GeographicArea {
Expand All @@ -834,10 +834,12 @@ class Example with _$Example {
}
```

In case you want to specify a generic mixin or interface you need to
declare it as a string using the `With.fromString` constructor,
`Implements.fromString` respectively. Similar `Street` is mixing with
`AdministrativeArea<House>`.
This also works for implementing or mixing in generic classes e.g.
`AdministrativeArea<House>` except when the class has a generic type parameter
e.g. `AdministrativeArea<T>`. In this case freezed will generate correct code
but dart will throw a load error on the annotation declaration when compiling.
To avoid this you should use the `@Implements.fromString` and
`@With.fromString` decorators as follows:

```dart
abstract class GeographicArea {}
Expand All @@ -846,16 +848,17 @@ abstract class Shop {}
abstract class AdministrativeArea<T> {}
@freezed
class Example with _$Example {
const factory Example.person(String name, int age) = Person;
class Example<T> with _$Example<T> {
const factory Example.person(String name, int age) = Person<T>;
@With<AdministrativeArea<House>>()
const factory Example.street(String name) = Street;
@With.fromString('AdministrativeArea<T>')
const factory Example.street(String name) = Street<T>;
@With<House>()
@Implements<Shop>()
@Implements<GeographicArea>()
const factory Example.city(String name, int population) = City;
@Implements.fromString('AdministrativeArea<T>')
const factory Example.city(String name, int population) = City<T>;
}
```

Expand Down
30 changes: 20 additions & 10 deletions packages/freezed/lib/src/freezed_generator.dart
Expand Up @@ -381,11 +381,16 @@ Read here: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/C
if (!metadata.isWith) continue;
final object = metadata.computeConstantValue()!;

yield resolveFullTypeStringFrom(
constructor.library,
(object.type! as InterfaceType).typeArguments.single,
withNullability: false,
);
final stringType = object.getField('stringType');
if (stringType?.isNull == false) {
yield stringType!.toStringValue()!;
} else {
yield resolveFullTypeStringFrom(
constructor.library,
(object.type! as InterfaceType).typeArguments.single,
withNullability: false,
);
}
}
}

Expand All @@ -396,11 +401,16 @@ Read here: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/C
if (!metadata.isImplements) continue;
final object = metadata.computeConstantValue()!;

yield resolveFullTypeStringFrom(
constructor.library,
(object.type! as InterfaceType).typeArguments.single,
withNullability: false,
);
final stringType = object.getField('stringType');
if (stringType?.isNull == false) {
yield stringType!.toStringValue()!;
} else {
yield resolveFullTypeStringFrom(
constructor.library,
(object.type! as InterfaceType).typeArguments.single,
withNullability: false,
);
}
}
}

Expand Down
Expand Up @@ -62,12 +62,35 @@ void main() {
});
});

test('Generic type', () {
const object = GenericImplements<int>.city('Morning', 140000);
expect(object, isA<House>());
expect(
object,
isA<GeographicArea>().having((s) => s.name, 'name', 'Morning'),
);
group('Generic type', () {
test('static @With and @Implements', () {
const object = GenericCity<int>('Morning', 140000);
// testing that GenericStreet's abstract class implements the types too
// ignore: unused_local_variable
House house = object;
// ignore: unused_local_variable
GeographicArea area = object;

expect(object, isA<House>());
expect(
object,
isA<GeographicArea>().having((s) => s.name, 'name', 'Morning'),
);
});

test('generic @With and @Implements using fromString', () {
const object = GenericStreet<int>('Oak', 42);
// testing that GenericStreet's abstract class implements the types too
// ignore: unused_local_variable
AdministrativeArea<int> area = object;
// ignore: unused_local_variable
Generic<int> generic = object;

expect(object, isA<AdministrativeArea<int>>());
expect(
object,
isA<Generic<int>>().having((s) => s.value, 'value', 42),
);
});
});
}
9 changes: 8 additions & 1 deletion packages/freezed/test/integration/implements_decorator.dart
Expand Up @@ -43,13 +43,20 @@ class GenericImplements<T> with _$GenericImplements<T> {
const factory GenericImplements.person(String name, int age) =
GenericPerson<T>;

@With.fromString('AdministrativeArea<T>')
@Implements.fromString('Generic<T>')
const factory GenericImplements.street(String name, T value) =
GenericStreet<T>;

@With<House>()
@Implements<GeographicArea>()
const factory GenericImplements.city(String name, int population) =
GenericCity<T>;
}

abstract class Generic<T> {}
abstract class Generic<T> {
T get value;
}

abstract class GeographicArea {
String get name;
Expand Down
38 changes: 32 additions & 6 deletions packages/freezed_annotation/lib/freezed_annotation.dart
Expand Up @@ -508,8 +508,8 @@ class Default {
final Object? defaultValue;
}

/// Marks a union type to implement the interface [type] or [stringType].
/// In the case below `City` will implement `GeographicArea`.
/// Marks a union type to implement the interface [stringType] or type T.
/// In the case below `City` will implement `AdministrativeArea<Hoouse>`.
/// ```dart
/// @freezed
/// abstract class Example with _$Example {
Expand All @@ -520,18 +520,31 @@ class Default {
/// }
/// ```
///
/// If interface is generic the `Implements.fromString` constructor must be used
/// otherwise freezed will generate correct codee but dart will throw a load
/// error on the annotation declaration.
///
/// Note: You need to make sure that you comply with the interface requirements
/// by implementing all the abstract members. If the interface has no members or
/// just fields you can fulfil the interface contract by adding them in the
/// constructor of the union type. Keep in mind that if the interface defines a
/// method or a getter, that you implement in the class, you need to use the
/// [Custom getters and methods](#custom-getters-and-methods) instructions.
class Implements<T> {
const Implements();
/// Marks a union type to implement the interface T.
const Implements() : stringType = null;

/// Marks a union type to implement the interface [stringType]. This
/// constructor must be used when implementing a generic class with a generic
/// type parameter e.g. `AdministrativeArea<T>` otherwise dart will throw a
/// load error when compiling the annotation.
const Implements.fromString(this.stringType);

final String? stringType;
}

/// Marks a union type to mixin the class [type] or [stringType].
/// In the case below `City` will mixin with `GeographicArea`.
/// Marks a union type to mixin the interface [stringType] or type T.
/// In the case below `City` will mixin with `AdministrativeArea<House>`.
/// ```dart
/// @freezed
/// abstract class Example with _$Example {
Expand All @@ -542,14 +555,27 @@ class Implements<T> {
/// }
/// ```
///
/// If interface is generic the `With.fromString` constructor must be used
/// otherwise freezed will generate correct code but dart will throw a load
/// error on the annotation declaration.
///
/// Note: You need to make sure that you comply with the interface requirements
/// by implementing all the abstract members. If the mixin has no members or
/// just fields, you can fulfil the interface contract by adding them in the
/// constructor of the union type. Keep in mind that if the mixin defines a
/// method or a getter, that you implement in the class, you need to use the
/// [Custom getters and methods](#custom-getters-and-methods) instructions.
class With<T> {
const With();
/// Marks a union type to mixin the interface T.
const With() : stringType = null;

/// Marks a union type to mixin the interface [stringType]. This constructor
/// must be used when mixing in a generic class with a generic type parameter
/// e.g. `AdministrativeArea<T>` otherwise dart will throw a load error when
/// compiling the annotation.
const With.fromString(this.stringType);

final String? stringType;
}

/// An annotation used to specify how a union type will be serialized.
Expand Down

0 comments on commit b2ca8a5

Please sign in to comment.