diff --git a/benchmarks/lib/src/copy_with.freezed.dart b/benchmarks/lib/src/copy_with.freezed.dart index 64b46473..d39b5892 100644 --- a/benchmarks/lib/src/copy_with.freezed.dart +++ b/benchmarks/lib/src/copy_with.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,139 +10,324 @@ part of 'copy_with.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$Model { - int get counter => throw _privateConstructorUsedError; + int get counter; + /// Create a copy of Model + /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $ModelCopyWith get copyWith => throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $ModelCopyWith get copyWith => + _$ModelCopyWithImpl(this as Model, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is Model && + (identical(other.counter, counter) || other.counter == counter)); + } + + @override + int get hashCode => Object.hash(runtimeType, counter); + + @override + String toString() { + return 'Model(counter: $counter)'; + } } /// @nodoc -abstract class $ModelCopyWith<$Res> { - factory $ModelCopyWith(Model value, $Res Function(Model) then) = - _$ModelCopyWithImpl<$Res, Model>; +abstract mixin class $ModelCopyWith<$Res> { + factory $ModelCopyWith(Model value, $Res Function(Model) _then) = + _$ModelCopyWithImpl; @useResult $Res call({int counter}); } /// @nodoc -class _$ModelCopyWithImpl<$Res, $Val extends Model> - implements $ModelCopyWith<$Res> { - _$ModelCopyWithImpl(this._value, this._then); +class _$ModelCopyWithImpl<$Res> implements $ModelCopyWith<$Res> { + _$ModelCopyWithImpl(this._self, this._then); - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; + final Model _self; + final $Res Function(Model) _then; + /// Create a copy of Model + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ Object? counter = null, }) { - return _then(_value.copyWith( + return _then(_self.copyWith( counter: null == counter - ? _value.counter + ? _self.counter : counter // ignore: cast_nullable_to_non_nullable as int, - ) as $Val); + )); } } -/// @nodoc -abstract class _$$ModelImplCopyWith<$Res> implements $ModelCopyWith<$Res> { - factory _$$ModelImplCopyWith( - _$ModelImpl value, $Res Function(_$ModelImpl) then) = - __$$ModelImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({int counter}); -} +/// Adds pattern-matching-related methods to [Model]. +extension ModelPatterns on Model { + /// A variant of `map` that fallback to returning `orElse`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` + + @optionalTypeArgs + TResult maybeMap( + TResult Function(_Model value)? $default, { + required TResult orElse(), + }) { + final _that = this; + switch (_that) { + case _Model() when $default != null: + return $default(_that); + case _: + return orElse(); + } + } -/// @nodoc -class __$$ModelImplCopyWithImpl<$Res> - extends _$ModelCopyWithImpl<$Res, _$ModelImpl> - implements _$$ModelImplCopyWith<$Res> { - __$$ModelImplCopyWithImpl( - _$ModelImpl _value, $Res Function(_$ModelImpl) _then) - : super(_value, _then); + /// A `switch`-like method, using callbacks. + /// + /// Callbacks receives the raw object, upcasted. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case final Subclass2 value: + /// return ...; + /// } + /// ``` + + @optionalTypeArgs + TResult map( + TResult Function(_Model value) $default, + ) { + final _that = this; + switch (_that) { + case _Model(): + return $default(_that); + case _: + throw StateError('Unexpected subclass'); + } + } - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? counter = null, + /// A variant of `map` that fallback to returning `null`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_Model value)? $default, + ) { + final _that = this; + switch (_that) { + case _Model() when $default != null: + return $default(_that); + case _: + return null; + } + } + + /// A variant of `when` that fallback to an `orElse` callback. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` + + @optionalTypeArgs + TResult maybeWhen( + TResult Function(int counter)? $default, { + required TResult orElse(), }) { - return _then(_$ModelImpl( - counter: null == counter - ? _value.counter - : counter // ignore: cast_nullable_to_non_nullable - as int, - )); + final _that = this; + switch (_that) { + case _Model() when $default != null: + return $default(_that.counter); + case _: + return orElse(); + } + } + + /// A `switch`-like method, using callbacks. + /// + /// As opposed to `map`, this offers destructuring. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case Subclass2(:final field2): + /// return ...; + /// } + /// ``` + + @optionalTypeArgs + TResult when( + TResult Function(int counter) $default, + ) { + final _that = this; + switch (_that) { + case _Model(): + return $default(_that.counter); + case _: + throw StateError('Unexpected subclass'); + } + } + + /// A variant of `when` that fallback to returning `null` + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function(int counter)? $default, + ) { + final _that = this; + switch (_that) { + case _Model() when $default != null: + return $default(_that.counter); + case _: + return null; + } } } /// @nodoc -class _$ModelImpl implements _Model { - _$ModelImpl({required this.counter}); +class _Model implements Model { + _Model({required this.counter}); @override final int counter; + /// Create a copy of Model + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'Model.Model(counter: $counter)'; - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$ModelCopyWith<_Model> get copyWith => + __$ModelCopyWithImpl<_Model>(this, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$ModelImpl && + other is _Model && (identical(other.counter, counter) || other.counter == counter)); } @override int get hashCode => Object.hash(runtimeType, counter); - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$ModelImplCopyWith<_$ModelImpl> get copyWith => - __$$ModelImplCopyWithImpl<_$ModelImpl>(this, _$identity); + String toString() { + return 'Model(counter: $counter)'; + } } -abstract class _Model implements Model { - factory _Model({required final int counter}) = _$ModelImpl; - +/// @nodoc +abstract mixin class _$ModelCopyWith<$Res> implements $ModelCopyWith<$Res> { + factory _$ModelCopyWith(_Model value, $Res Function(_Model) _then) = + __$ModelCopyWithImpl; @override - int get counter; + @useResult + $Res call({int counter}); +} + +/// @nodoc +class __$ModelCopyWithImpl<$Res> implements _$ModelCopyWith<$Res> { + __$ModelCopyWithImpl(this._self, this._then); + + final _Model _self; + final $Res Function(_Model) _then; + + /// Create a copy of Model + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$ModelImplCopyWith<_$ModelImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $Res call({ + Object? counter = null, + }) { + return _then(_Model( + counter: null == counter + ? _self.counter + : counter // ignore: cast_nullable_to_non_nullable + as int, + )); + } } /// @nodoc mixin _$ModelWrapper { - Model get model => throw _privateConstructorUsedError; + Model get model; + /// Create a copy of ModelWrapper + /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $ModelWrapperCopyWith get copyWith => - throw _privateConstructorUsedError; + _$ModelWrapperCopyWithImpl( + this as ModelWrapper, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is ModelWrapper && + (identical(other.model, model) || other.model == model)); + } + + @override + int get hashCode => Object.hash(runtimeType, model); + + @override + String toString() { + return 'ModelWrapper(model: $model)'; + } } /// @nodoc -abstract class $ModelWrapperCopyWith<$Res> { +abstract mixin class $ModelWrapperCopyWith<$Res> { factory $ModelWrapperCopyWith( - ModelWrapper value, $Res Function(ModelWrapper) then) = - _$ModelWrapperCopyWithImpl<$Res, ModelWrapper>; + ModelWrapper value, $Res Function(ModelWrapper) _then) = + _$ModelWrapperCopyWithImpl; @useResult $Res call({Model model}); @@ -149,111 +335,274 @@ abstract class $ModelWrapperCopyWith<$Res> { } /// @nodoc -class _$ModelWrapperCopyWithImpl<$Res, $Val extends ModelWrapper> - implements $ModelWrapperCopyWith<$Res> { - _$ModelWrapperCopyWithImpl(this._value, this._then); +class _$ModelWrapperCopyWithImpl<$Res> implements $ModelWrapperCopyWith<$Res> { + _$ModelWrapperCopyWithImpl(this._self, this._then); - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; + final ModelWrapper _self; + final $Res Function(ModelWrapper) _then; + /// Create a copy of ModelWrapper + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ Object? model = null, }) { - return _then(_value.copyWith( + return _then(_self.copyWith( model: null == model - ? _value.model + ? _self.model : model // ignore: cast_nullable_to_non_nullable as Model, - ) as $Val); + )); } + /// Create a copy of ModelWrapper + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $ModelCopyWith<$Res> get model { - return $ModelCopyWith<$Res>(_value.model, (value) { - return _then(_value.copyWith(model: value) as $Val); + return $ModelCopyWith<$Res>(_self.model, (value) { + return _then(_self.copyWith(model: value)); }); } } -/// @nodoc -abstract class _$$ModelWrapperImplCopyWith<$Res> - implements $ModelWrapperCopyWith<$Res> { - factory _$$ModelWrapperImplCopyWith( - _$ModelWrapperImpl value, $Res Function(_$ModelWrapperImpl) then) = - __$$ModelWrapperImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({Model model}); +/// Adds pattern-matching-related methods to [ModelWrapper]. +extension ModelWrapperPatterns on ModelWrapper { + /// A variant of `map` that fallback to returning `orElse`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` + + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ModelWrapper value)? $default, { + required TResult orElse(), + }) { + final _that = this; + switch (_that) { + case _ModelWrapper() when $default != null: + return $default(_that); + case _: + return orElse(); + } + } - @override - $ModelCopyWith<$Res> get model; -} + /// A `switch`-like method, using callbacks. + /// + /// Callbacks receives the raw object, upcasted. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case final Subclass2 value: + /// return ...; + /// } + /// ``` + + @optionalTypeArgs + TResult map( + TResult Function(_ModelWrapper value) $default, + ) { + final _that = this; + switch (_that) { + case _ModelWrapper(): + return $default(_that); + case _: + throw StateError('Unexpected subclass'); + } + } -/// @nodoc -class __$$ModelWrapperImplCopyWithImpl<$Res> - extends _$ModelWrapperCopyWithImpl<$Res, _$ModelWrapperImpl> - implements _$$ModelWrapperImplCopyWith<$Res> { - __$$ModelWrapperImplCopyWithImpl( - _$ModelWrapperImpl _value, $Res Function(_$ModelWrapperImpl) _then) - : super(_value, _then); + /// A variant of `map` that fallback to returning `null`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ModelWrapper value)? $default, + ) { + final _that = this; + switch (_that) { + case _ModelWrapper() when $default != null: + return $default(_that); + case _: + return null; + } + } - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? model = null, + /// A variant of `when` that fallback to an `orElse` callback. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` + + @optionalTypeArgs + TResult maybeWhen( + TResult Function(Model model)? $default, { + required TResult orElse(), }) { - return _then(_$ModelWrapperImpl( - model: null == model - ? _value.model - : model // ignore: cast_nullable_to_non_nullable - as Model, - )); + final _that = this; + switch (_that) { + case _ModelWrapper() when $default != null: + return $default(_that.model); + case _: + return orElse(); + } + } + + /// A `switch`-like method, using callbacks. + /// + /// As opposed to `map`, this offers destructuring. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case Subclass2(:final field2): + /// return ...; + /// } + /// ``` + + @optionalTypeArgs + TResult when( + TResult Function(Model model) $default, + ) { + final _that = this; + switch (_that) { + case _ModelWrapper(): + return $default(_that.model); + case _: + throw StateError('Unexpected subclass'); + } + } + + /// A variant of `when` that fallback to returning `null` + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function(Model model)? $default, + ) { + final _that = this; + switch (_that) { + case _ModelWrapper() when $default != null: + return $default(_that.model); + case _: + return null; + } } } /// @nodoc -class _$ModelWrapperImpl implements _ModelWrapper { - _$ModelWrapperImpl({required this.model}); +class _ModelWrapper implements ModelWrapper { + _ModelWrapper({required this.model}); @override final Model model; + /// Create a copy of ModelWrapper + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'ModelWrapper.ModelWrapper(model: $model)'; - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$ModelWrapperCopyWith<_ModelWrapper> get copyWith => + __$ModelWrapperCopyWithImpl<_ModelWrapper>(this, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$ModelWrapperImpl && + other is _ModelWrapper && (identical(other.model, model) || other.model == model)); } @override int get hashCode => Object.hash(runtimeType, model); - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$ModelWrapperImplCopyWith<_$ModelWrapperImpl> get copyWith => - __$$ModelWrapperImplCopyWithImpl<_$ModelWrapperImpl>(this, _$identity); + String toString() { + return 'ModelWrapper(model: $model)'; + } } -abstract class _ModelWrapper implements ModelWrapper { - factory _ModelWrapper({required final Model model}) = _$ModelWrapperImpl; +/// @nodoc +abstract mixin class _$ModelWrapperCopyWith<$Res> + implements $ModelWrapperCopyWith<$Res> { + factory _$ModelWrapperCopyWith( + _ModelWrapper value, $Res Function(_ModelWrapper) _then) = + __$ModelWrapperCopyWithImpl; + @override + @useResult + $Res call({Model model}); @override - Model get model; + $ModelCopyWith<$Res> get model; +} + +/// @nodoc +class __$ModelWrapperCopyWithImpl<$Res> + implements _$ModelWrapperCopyWith<$Res> { + __$ModelWrapperCopyWithImpl(this._self, this._then); + + final _ModelWrapper _self; + final $Res Function(_ModelWrapper) _then; + + /// Create a copy of ModelWrapper + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$ModelWrapperImplCopyWith<_$ModelWrapperImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $Res call({ + Object? model = null, + }) { + return _then(_ModelWrapper( + model: null == model + ? _self.model + : model // ignore: cast_nullable_to_non_nullable + as Model, + )); + } + + /// Create a copy of ModelWrapper + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ModelCopyWith<$Res> get model { + return $ModelCopyWith<$Res>(_self.model, (value) { + return _then(_self.copyWith(model: value)); + }); + } } + +// dart format on diff --git a/benchmarks/lib/src/equal.freezed.dart b/benchmarks/lib/src/equal.freezed.dart index 13300173..4205236b 100644 --- a/benchmarks/lib/src/equal.freezed.dart +++ b/benchmarks/lib/src/equal.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,102 +10,240 @@ part of 'equal.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$ModelWithList { - List get someList => throw _privateConstructorUsedError; - int get counter => throw _privateConstructorUsedError; + List get someList; + int get counter; + /// Create a copy of ModelWithList + /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $ModelWithListCopyWith get copyWith => - throw _privateConstructorUsedError; + _$ModelWithListCopyWithImpl( + this as ModelWithList, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is ModelWithList && + const DeepCollectionEquality().equals(other.someList, someList) && + (identical(other.counter, counter) || other.counter == counter)); + } + + @override + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(someList), counter); + + @override + String toString() { + return 'ModelWithList(someList: $someList, counter: $counter)'; + } } /// @nodoc -abstract class $ModelWithListCopyWith<$Res> { +abstract mixin class $ModelWithListCopyWith<$Res> { factory $ModelWithListCopyWith( - ModelWithList value, $Res Function(ModelWithList) then) = - _$ModelWithListCopyWithImpl<$Res, ModelWithList>; + ModelWithList value, $Res Function(ModelWithList) _then) = + _$ModelWithListCopyWithImpl; @useResult $Res call({List someList, int counter}); } /// @nodoc -class _$ModelWithListCopyWithImpl<$Res, $Val extends ModelWithList> +class _$ModelWithListCopyWithImpl<$Res> implements $ModelWithListCopyWith<$Res> { - _$ModelWithListCopyWithImpl(this._value, this._then); + _$ModelWithListCopyWithImpl(this._self, this._then); - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; + final ModelWithList _self; + final $Res Function(ModelWithList) _then; + /// Create a copy of ModelWithList + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ Object? someList = null, Object? counter = null, }) { - return _then(_value.copyWith( + return _then(_self.copyWith( someList: null == someList - ? _value.someList + ? _self.someList : someList // ignore: cast_nullable_to_non_nullable as List, counter: null == counter - ? _value.counter + ? _self.counter : counter // ignore: cast_nullable_to_non_nullable as int, - ) as $Val); + )); } } -/// @nodoc -abstract class _$$ModelWithListImplCopyWith<$Res> - implements $ModelWithListCopyWith<$Res> { - factory _$$ModelWithListImplCopyWith( - _$ModelWithListImpl value, $Res Function(_$ModelWithListImpl) then) = - __$$ModelWithListImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({List someList, int counter}); -} +/// Adds pattern-matching-related methods to [ModelWithList]. +extension ModelWithListPatterns on ModelWithList { + /// A variant of `map` that fallback to returning `orElse`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` -/// @nodoc -class __$$ModelWithListImplCopyWithImpl<$Res> - extends _$ModelWithListCopyWithImpl<$Res, _$ModelWithListImpl> - implements _$$ModelWithListImplCopyWith<$Res> { - __$$ModelWithListImplCopyWithImpl( - _$ModelWithListImpl _value, $Res Function(_$ModelWithListImpl) _then) - : super(_value, _then); + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ModelWithList value)? $default, { + required TResult orElse(), + }) { + final _that = this; + switch (_that) { + case _ModelWithList() when $default != null: + return $default(_that); + case _: + return orElse(); + } + } - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? someList = null, - Object? counter = null, + /// A `switch`-like method, using callbacks. + /// + /// Callbacks receives the raw object, upcasted. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case final Subclass2 value: + /// return ...; + /// } + /// ``` + + @optionalTypeArgs + TResult map( + TResult Function(_ModelWithList value) $default, + ) { + final _that = this; + switch (_that) { + case _ModelWithList(): + return $default(_that); + case _: + throw StateError('Unexpected subclass'); + } + } + + /// A variant of `map` that fallback to returning `null`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ModelWithList value)? $default, + ) { + final _that = this; + switch (_that) { + case _ModelWithList() when $default != null: + return $default(_that); + case _: + return null; + } + } + + /// A variant of `when` that fallback to an `orElse` callback. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` + + @optionalTypeArgs + TResult maybeWhen( + TResult Function(List someList, int counter)? $default, { + required TResult orElse(), }) { - return _then(_$ModelWithListImpl( - someList: null == someList - ? _value._someList - : someList // ignore: cast_nullable_to_non_nullable - as List, - counter: null == counter - ? _value.counter - : counter // ignore: cast_nullable_to_non_nullable - as int, - )); + final _that = this; + switch (_that) { + case _ModelWithList() when $default != null: + return $default(_that.someList, _that.counter); + case _: + return orElse(); + } + } + + /// A `switch`-like method, using callbacks. + /// + /// As opposed to `map`, this offers destructuring. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case Subclass2(:final field2): + /// return ...; + /// } + /// ``` + + @optionalTypeArgs + TResult when( + TResult Function(List someList, int counter) $default, + ) { + final _that = this; + switch (_that) { + case _ModelWithList(): + return $default(_that.someList, _that.counter); + case _: + throw StateError('Unexpected subclass'); + } + } + + /// A variant of `when` that fallback to returning `null` + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function(List someList, int counter)? $default, + ) { + final _that = this; + switch (_that) { + case _ModelWithList() when $default != null: + return $default(_that.someList, _that.counter); + case _: + return null; + } } } /// @nodoc -class _$ModelWithListImpl implements _ModelWithList { - _$ModelWithListImpl( - {final List someList = const [], this.counter = 0}) +class _ModelWithList implements ModelWithList { + _ModelWithList({final List someList = const [], this.counter = 0}) : _someList = someList; final List _someList; @@ -120,16 +259,19 @@ class _$ModelWithListImpl implements _ModelWithList { @JsonKey() final int counter; + /// Create a copy of ModelWithList + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'ModelWithList.ModelWithList(someList: $someList, counter: $counter)'; - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$ModelWithListCopyWith<_ModelWithList> get copyWith => + __$ModelWithListCopyWithImpl<_ModelWithList>(this, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$ModelWithListImpl && + other is _ModelWithList && const DeepCollectionEquality().equals(other._someList, _someList) && (identical(other.counter, counter) || other.counter == counter)); } @@ -138,23 +280,50 @@ class _$ModelWithListImpl implements _ModelWithList { int get hashCode => Object.hash( runtimeType, const DeepCollectionEquality().hash(_someList), counter); - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$ModelWithListImplCopyWith<_$ModelWithListImpl> get copyWith => - __$$ModelWithListImplCopyWithImpl<_$ModelWithListImpl>(this, _$identity); + String toString() { + return 'ModelWithList(someList: $someList, counter: $counter)'; + } } -abstract class _ModelWithList implements ModelWithList { - factory _ModelWithList({final List someList, final int counter}) = - _$ModelWithListImpl; - - @override - List get someList; +/// @nodoc +abstract mixin class _$ModelWithListCopyWith<$Res> + implements $ModelWithListCopyWith<$Res> { + factory _$ModelWithListCopyWith( + _ModelWithList value, $Res Function(_ModelWithList) _then) = + __$ModelWithListCopyWithImpl; @override - int get counter; + @useResult + $Res call({List someList, int counter}); +} + +/// @nodoc +class __$ModelWithListCopyWithImpl<$Res> + implements _$ModelWithListCopyWith<$Res> { + __$ModelWithListCopyWithImpl(this._self, this._then); + + final _ModelWithList _self; + final $Res Function(_ModelWithList) _then; + + /// Create a copy of ModelWithList + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$ModelWithListImplCopyWith<_$ModelWithListImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $Res call({ + Object? someList = null, + Object? counter = null, + }) { + return _then(_ModelWithList( + someList: null == someList + ? _self._someList + : someList // ignore: cast_nullable_to_non_nullable + as List, + counter: null == counter + ? _self.counter + : counter // ignore: cast_nullable_to_non_nullable + as int, + )); + } } + +// dart format on diff --git a/benchmarks/pubspec.yaml b/benchmarks/pubspec.yaml index 760ae617..3f7a36de 100644 --- a/benchmarks/pubspec.yaml +++ b/benchmarks/pubspec.yaml @@ -2,7 +2,7 @@ name: example description: A new Flutter project. environment: - sdk: ">=2.14.0 <3.0.0" + sdk: ^3.5.0 dependencies: flutter: diff --git a/packages/freezed/CHANGELOG.md b/packages/freezed/CHANGELOG.md index bf8c678c..da139a99 100644 --- a/packages/freezed/CHANGELOG.md +++ b/packages/freezed/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased minor + +Added `when`/`map` back + ## 3.0.6 - 2025-04-05 Remove logs diff --git a/packages/freezed/README.md b/packages/freezed/README.md index c7f686d0..968784e6 100644 --- a/packages/freezed/README.md +++ b/packages/freezed/README.md @@ -61,6 +61,9 @@ to focus on the definition of your model. - [Using pattern matching to read non-shared properties](#using-pattern-matching-to-read-non-shared-properties) - [Mixins and Interfaces for individual classes for union types](#mixins-and-interfaces-for-individual-classes-for-union-types) - [Ejecting an individual union case](#ejecting-an-individual-union-case) + - [(Legacy) Pattern matching utilities](#legacy-pattern-matching-utilities) + - [When](#when) + - [Map](#map) - [Configurations](#configurations) - [Changing the behavior for a specific model](#changing-the-behavior-for-a-specific-model) - [Changing the behavior for the entire project](#changing-the-behavior-for-the-entire-project) @@ -1187,6 +1190,124 @@ class ResultData extends Result with _$ResultData { } ``` +#### (Legacy) Pattern matching utilities + +> [!WARNING] +> As of Dart 3, Dart now has built-in pattern-matching using sealed classes. +> As such, you no-longer need to rely on Freezed's generated methods for pattern matching. +> Instead of using `when`/`map`, use the official Dart syntax. +> +> The references to `when`/`map` are kept for users who have yet to +> migrate to Dart 3. +> But in the long term, you should stop relying on them and migrate to `switch` expressions. + +##### When + +The [when] method is the equivalent to pattern matching with destructing. +The prototype of the method depends on the constructors defined. + +For example, with: + +```dart +@freezed +sealed class Union with _$Union { + const factory Union(int value) = Data; + const factory Union.loading() = Loading; + const factory Union.error([String? message]) = ErrorDetails; +} +``` + +Then [when] will be: + +```dart +var union = Union(42); + +print( + union.when( + (int value) => 'Data $value', + loading: () => 'loading', + error: (String? message) => 'Error: $message', + ), +); // Data 42 +``` + +Whereas if we defined: + +```dart +@freezed +sealed class Model with _$Model { + factory Model.first(String a) = First; + factory Model.second(int b, bool c) = Second; +} +``` + +Then [when] will be: + +```dart +var model = Model.first('42'); + +print( + model.when( + first: (String a) => 'first $a', + second: (int b, bool c) => 'second $b $c' + ), +); // first 42 +``` + +Notice how each callback matches with a constructor's name and prototype. + +##### Map + +The [map] methods are equivalent to [when], but **without** destructuring. + +Consider this class: + +```dart +@freezed +sealed class Model with _$Model { + factory Model.first(String a) = First; + factory Model.second(int b, bool c) = Second; +} +``` + +With such class, while [when] will be: + +```dart +var model = Model.first('42'); + +print( + model.when( + first: (String a) => 'first $a', + second: (int b, bool c) => 'second $b $c' + ), +); // first 42 +``` + +[map] will instead be: + +```dart +var model = Model.first('42'); + +print( + model.map( + first: (First value) => 'first ${value.a}', + second: (Second value) => 'second ${value.b} ${value.c}' + ), +); // first 42 +``` + +This can be useful if you want to do complex operations, like [copyWith]/`toString` for example: + +```dart +var model = Model.second(42, false) +print( + model.map( + first: (value) => value, + second: (value) => value.copyWith(c: true), + ) +); // Model.second(b: 42, c: true) +``` + ## Configurations Freezed offers various options to customize the generated code. To do so, there are two possibilities: diff --git a/packages/freezed/lib/src/freezed_generator.dart b/packages/freezed/lib/src/freezed_generator.dart index 9aaaaeeb..aed14498 100644 --- a/packages/freezed/lib/src/freezed_generator.dart +++ b/packages/freezed/lib/src/freezed_generator.dart @@ -1,6 +1,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:collection/collection.dart'; import 'package:freezed/src/templates/copy_with.dart'; +import 'package:freezed/src/templates/pattern_template.dart'; import 'package:freezed/src/tools/type.dart'; import 'package:freezed_annotation/freezed_annotation.dart' show Freezed; import 'package:meta/meta.dart'; @@ -106,6 +107,8 @@ class FreezedGenerator extends ParserGenerator { globalData: globalData, ); + yield patterns(data); + for (final constructor in data.constructors) { yield Concrete( data: data, diff --git a/packages/freezed/lib/src/models.dart b/packages/freezed/lib/src/models.dart index dd7457fb..fdb7a251 100644 --- a/packages/freezed/lib/src/models.dart +++ b/packages/freezed/lib/src/models.dart @@ -387,6 +387,30 @@ When specifying fields in non-factory constructor then specifying factory constr } } +class MapConfig { + MapConfig({ + required this.map, + required this.mapOrNull, + required this.maybeMap, + }); + + final bool map; + final bool mapOrNull; + final bool maybeMap; +} + +class WhenConfig { + WhenConfig({ + required this.when, + required this.whenOrNull, + required this.maybeWhen, + }); + + final bool when; + final bool whenOrNull; + final bool maybeWhen; +} + class ImplementsAnnotation { ImplementsAnnotation({required this.type}); @@ -1051,6 +1075,7 @@ To fix, either: bool get hasSuperEqual => _node.hasSuperEqual; bool get hasSuperHashCode => _node.hasSuperHashCode; + bool get isSealed => _node.sealedKeyword != null; String get escapedName { var generics = @@ -1097,6 +1122,8 @@ class ClassConfig { required this.asString, required this.fromJson, required this.toJson, + required this.map, + required this.when, required this.asUnmodifiableCollections, required this.genericArgumentFactories, required this.annotation, @@ -1114,8 +1141,25 @@ class ClassConfig { library, ); + final shouldGeneratePatterns = declaration.constructors + .where( + (e) => !e.isManualCtor && !(e.name?.lexeme ?? '').startsWith('_'), + ) + .isNotEmpty; + return ClassConfig( equal: resolvedAnnotation.equal ?? !declaration.hasCustomEquals, + map: MapConfig( + map: resolvedAnnotation.map?.map ?? shouldGeneratePatterns, + mapOrNull: resolvedAnnotation.map?.mapOrNull ?? shouldGeneratePatterns, + maybeMap: resolvedAnnotation.map?.maybeMap ?? shouldGeneratePatterns, + ), + when: WhenConfig( + when: resolvedAnnotation.when?.when ?? shouldGeneratePatterns, + whenOrNull: + resolvedAnnotation.when?.whenOrNull ?? shouldGeneratePatterns, + maybeWhen: resolvedAnnotation.when?.maybeWhen ?? shouldGeneratePatterns, + ), asString: resolvedAnnotation.toStringOverride ?? !declaration.hasCustomToString, fromJson: resolvedAnnotation.fromJson ?? needsJsonSerializable, @@ -1187,6 +1231,52 @@ class ClassConfig { }, orElse: () => globalConfigs.unionValueCase, ), + when: annotation.decodeField( + 'when', + decode: (obj) { + return FreezedWhenOptions( + when: obj.decodeField( + 'when', + decode: (obj) => obj.toBoolValue(), + orElse: () => globalConfigs.when?.when, + ), + maybeWhen: obj.decodeField( + 'maybeWhen', + decode: (obj) => obj.toBoolValue(), + orElse: () => globalConfigs.when?.maybeWhen, + ), + whenOrNull: obj.decodeField( + 'whenOrNull', + decode: (obj) => obj.toBoolValue(), + orElse: () => globalConfigs.when?.whenOrNull, + ), + ); + }, + orElse: () => globalConfigs.when, + ), + map: annotation.decodeField( + 'map', + decode: (obj) { + return FreezedMapOptions( + map: obj.decodeField( + 'map', + decode: (obj) => obj.toBoolValue(), + orElse: () => globalConfigs.map?.map, + ), + maybeMap: obj.decodeField( + 'maybeMap', + decode: (obj) => obj.toBoolValue(), + orElse: () => globalConfigs.map?.maybeMap, + ), + mapOrNull: obj.decodeField( + 'mapOrNull', + decode: (obj) => obj.toBoolValue(), + orElse: () => globalConfigs.map?.mapOrNull, + ), + ); + }, + orElse: () => globalConfigs.map, + ), ); } @@ -1194,6 +1284,8 @@ class ClassConfig { final bool asString; final bool fromJson; final bool toJson; + final MapConfig map; + final WhenConfig when; final bool asUnmodifiableCollections; final bool genericArgumentFactories; final Freezed annotation; diff --git a/packages/freezed/lib/src/templates/concrete_template.dart b/packages/freezed/lib/src/templates/concrete_template.dart index 08b2aada..27adde57 100644 --- a/packages/freezed/lib/src/templates/concrete_template.dart +++ b/packages/freezed/lib/src/templates/concrete_template.dart @@ -11,12 +11,6 @@ import 'copy_with.dart'; import 'parameter_template.dart'; import 'prototypes.dart'; -sealed class Foo {} - -class Bar extends Foo {} - -class Baz extends Foo {} - class Concrete { Concrete({ required this.constructor, diff --git a/packages/freezed/lib/src/templates/pattern_template.dart b/packages/freezed/lib/src/templates/pattern_template.dart new file mode 100644 index 00000000..43c925f6 --- /dev/null +++ b/packages/freezed/lib/src/templates/pattern_template.dart @@ -0,0 +1,404 @@ +import 'package:analyzer/dart/element/type_provider.dart'; +import 'package:freezed/src/models.dart'; +import 'package:freezed/src/templates/parameter_template.dart'; +import 'package:freezed/src/templates/prototypes.dart'; + +String patterns(Class data) { + final typeProvider = data.library.typeProvider; + final buffer = StringBuffer() + ..write(_maybeMap(data, typeProvider: typeProvider)) + ..write(_map(data, typeProvider: typeProvider)) + ..write(_mapOrNull(data, typeProvider: typeProvider)) + ..write(_maybeWhen(data, typeProvider: typeProvider)) + ..write(_when(data, typeProvider: typeProvider)) + ..write(_whenOrNull(data, typeProvider: typeProvider)); + + if (buffer.isNotEmpty) { + return ''' +/// Adds pattern-matching-related methods to [${data.name}]. +extension ${data.name}Patterns${data.genericsDefinitionTemplate} on ${data.name}${data.genericsParameterTemplate} { +$buffer +} +'''; + } + + return ''; +} + +String _maybeMap( + Class data, { + required TypeProvider typeProvider, +}) { + if (!data.options.map.maybeMap) return ''; + + return _mapImpl( + data, + doc: ''' +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` +''', + areCallbacksRequired: false, + isReturnTypeNullable: false, + name: 'maybeMap', + typeProvider: typeProvider, + ); +} + +String _map( + Class data, { + required TypeProvider typeProvider, +}) { + if (!data.options.map.map) return ''; + + return _mapImpl( + data, + doc: ''' +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` +''', + areCallbacksRequired: true, + isReturnTypeNullable: false, + name: 'map', + typeProvider: typeProvider, + ); +} + +String _mapOrNull( + Class data, { + required TypeProvider typeProvider, +}) { + if (!data.options.map.mapOrNull) return ''; + + return _mapImpl( + data, + doc: ''' +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` +''', + areCallbacksRequired: false, + isReturnTypeNullable: true, + name: 'mapOrNull', + typeProvider: typeProvider, + ); +} + +String _maybeWhen( + Class data, { + required TypeProvider typeProvider, +}) { + if (!data.options.when.maybeWhen) return ''; + + return _whenImpl( + data, + doc: ''' +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` +''', + areCallbacksRequired: false, + isReturnTypeNullable: false, + name: 'maybeWhen', + typeProvider: typeProvider, + ); +} + +String _when( + Class data, { + required TypeProvider typeProvider, +}) { + if (!data.options.when.when) return ''; + + return _whenImpl( + data, + doc: ''' +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` +''', + areCallbacksRequired: true, + isReturnTypeNullable: false, + name: 'when', + typeProvider: typeProvider, + ); +} + +String _whenOrNull( + Class data, { + required TypeProvider typeProvider, +}) { + if (!data.options.when.whenOrNull) return ''; + + return _whenImpl( + data, + doc: ''' +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` +''', + areCallbacksRequired: false, + isReturnTypeNullable: true, + name: 'whenOrNull', + typeProvider: typeProvider, + ); +} + +String _mapImpl( + Class data, { + required bool areCallbacksRequired, + required bool isReturnTypeNullable, + required String name, + required String doc, + required TypeProvider typeProvider, +}) { + final proto = _unionPrototype( + data.constructors, + areCallbacksRequired: areCallbacksRequired, + isReturnTypeNullable: isReturnTypeNullable, + name: name, + doc: doc, + typeProvider: typeProvider, + ctor2parameters: (constructor) { + return ParametersTemplate([ + Parameter( + name: 'value', + type: typeProvider.objectType, + typeDisplayString: + '${constructor.redirectedName}${data.genericsParameterTemplate}', + isRequired: false, + isFinal: false, + decorators: const [], + defaultValueSource: '', + doc: '', + showDefaultValue: false, + parameterElement: null, + ), + ]); + }, + ); + + final buffer = StringBuffer(proto) + ..writeln('{') + ..writeln('final _that = this;') + ..writeln('switch (_that) {'); + for (final constructor in data.constructors) { + buffer.write('case ${constructor.redirectedName}()'); + if (!areCallbacksRequired) { + buffer.write(' when ${constructor.callbackName} != null'); + } + buffer + ..writeln(':') + ..write('return ${constructor.callbackName}(_that);'); + } + + if (isReturnTypeNullable) { + buffer.writeln(''' +case _: + return null; +'''); + } else if (!areCallbacksRequired) { + buffer.writeln(''' +case _: + return orElse(); +'''); + } else if (!data.isSealed) { + buffer.writeln(''' +case _: + throw StateError('Unexpected subclass'); +'''); + } + + buffer + ..writeln('}') + ..writeln('}'); + + return buffer.toString(); +} + +String _whenImpl( + Class data, { + required bool areCallbacksRequired, + required bool isReturnTypeNullable, + required String name, + required String doc, + required TypeProvider typeProvider, +}) { + final proto = _unionPrototype( + data.constructors, + doc: doc, + areCallbacksRequired: areCallbacksRequired, + isReturnTypeNullable: isReturnTypeNullable, + name: name, + typeProvider: typeProvider, + ctor2parameters: (constructor) { + return ParametersTemplate([ + ...constructor.parameters.requiredPositionalParameters + .map((e) => e.copyWith(isFinal: false)), + ...constructor.parameters.optionalPositionalParameters + .map((e) => e.copyWith( + isFinal: false, + showDefaultValue: false, + )), + ...constructor.parameters.namedParameters.map((e) => e.copyWith( + isRequired: false, + isFinal: false, + showDefaultValue: false, + )), + ]); + }, + ); + + final buffer = StringBuffer('$proto {') + ..writeln('final _that = this;') + ..writeln('switch (_that) {'); + + for (final constructor in data.constructors) { + buffer.write('case ${constructor.redirectedName}()'); + if (!areCallbacksRequired) { + buffer.write(' when ${constructor.callbackName} != null'); + } + + final callbackParameters = + constructor.properties.map((e) => '_that.${e.name}').join(','); + + buffer + ..writeln(':') + ..write('return ${constructor.callbackName}($callbackParameters);'); + } + + if (isReturnTypeNullable) { + buffer.writeln(''' +case _: + return null; +'''); + } else if (!areCallbacksRequired) { + buffer.writeln(''' +case _: + return orElse(); +'''); + } else if (!data.isSealed) { + buffer.writeln(''' +case _: + throw StateError('Unexpected subclass'); +'''); + } + + buffer + ..writeln('}') + ..writeln('}'); + + return buffer.toString(); +} + +String _unionPrototype( + List allConstructors, { + required bool areCallbacksRequired, + required bool isReturnTypeNullable, + required String name, + required String doc, + required ParametersTemplate Function(ConstructorDetails) ctor2parameters, + required TypeProvider typeProvider, +}) { + final returnType = isReturnTypeNullable ? 'TResult?' : 'TResult'; + + final buffer = StringBuffer( + '$doc\n@optionalTypeArgs $returnType $name(', + ); + + final parameters = []; + for (final constructor in allConstructors) { + var template = CallbackParameter( + name: constructorNameToCallbackName(constructor.name), + type: areCallbacksRequired + ? typeProvider.objectType + : typeProvider.nullType, + typeDisplayString: returnType, + isFinal: false, + isRequired: !constructor.isDefault && areCallbacksRequired, + parameters: ctor2parameters(constructor), + decorators: const [], + defaultValueSource: '', + doc: '', + parameterElement: null, + ); + + if (constructor.isDefault) { + buffer + ..write(template) + ..write(','); + } else { + parameters.add(template); + } + } + + final hasOrElse = !areCallbacksRequired && !isReturnTypeNullable; + + if (parameters.isNotEmpty || hasOrElse) { + buffer.write('{'); + if (parameters.isNotEmpty) { + buffer + ..writeAll(parameters, ',') + ..write(','); + } + + if (hasOrElse) { + buffer.write('required $returnType orElse(),'); + } + + buffer.write('}'); + } + buffer.write(')'); + return buffer.toString(); +} diff --git a/packages/freezed/lib/src/tools/type.dart b/packages/freezed/lib/src/tools/type.dart index ee50e2c3..15147465 100644 --- a/packages/freezed/lib/src/tools/type.dart +++ b/packages/freezed/lib/src/tools/type.dart @@ -11,7 +11,9 @@ extension DartTypeX on DartType { } bool get isNullable { - if (isDynamic2 || nullabilitySuffix == NullabilitySuffix.question) { + if (isDynamic2 || + isDartCoreNull || + nullabilitySuffix == NullabilitySuffix.question) { return true; } diff --git a/packages/freezed/test/generic_test.dart b/packages/freezed/test/generic_test.dart index 5b7f24f5..e05f8c38 100644 --- a/packages/freezed/test/generic_test.dart +++ b/packages/freezed/test/generic_test.dart @@ -59,6 +59,27 @@ void main() { expect(model.value, 42); }); + test('map is generic too', () { + var result = MultipleConstructors(false) + .map>( + (Default value) => value, + first: (First value) => value, + second: (Second value) => value, + ); + + expect(result, MultipleConstructors(false)); + + MultipleConstructors(false) + .maybeMap>( + (Default value) => value, + first: (First value) => value, + second: (Second value) => value, + orElse: () => throw Error(), + ); + + expect(result, MultipleConstructors(false)); + }); + test('is generic2', () { MultiGeneric> value = MultiGeneric(Model(42)); Model model = value.model; diff --git a/packages/freezed/test/integration/optional_maybe.dart b/packages/freezed/test/integration/optional_maybe.dart index 6d182592..a6a52f42 100644 --- a/packages/freezed/test/integration/optional_maybe.dart +++ b/packages/freezed/test/integration/optional_maybe.dart @@ -3,6 +3,18 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'optional_maybe.freezed.dart'; part 'optional_maybe.g.dart'; +@Freezed(map: FreezedMapOptions.none) +abstract class OptionalMaybeMap with _$OptionalMaybeMap { + const factory OptionalMaybeMap.first() = OptionalMaybeMap1; + const factory OptionalMaybeMap.second() = OptionalMaybeMap2; +} + +@Freezed(when: FreezedWhenOptions.none) +abstract class OptionalMaybeWhen with _$OptionalMaybeWhen { + const factory OptionalMaybeWhen.first() = OptionalMaybeWhen1; + const factory OptionalMaybeWhen.second() = OptionalMaybeWhen2; +} + @Freezed(copyWith: false) abstract class OptionalCopyWith with _$OptionalCopyWith { const factory OptionalCopyWith([int? a]) = _OptionalCopyWith; @@ -18,6 +30,16 @@ class OptionalEqual with _$OptionalEqual { factory OptionalEqual() = _OptionalEqual; } +@Freezed(map: FreezedMapOptions.all, when: FreezedWhenOptions.all) +abstract class ForceUnionMethod with _$ForceUnionMethod { + factory ForceUnionMethod() = _ForceUnionMethod; +} + +@Freezed(map: FreezedMapOptions.all, when: FreezedWhenOptions.all) +abstract class ForceUnionMethod2 with _$ForceUnionMethod2 { + factory ForceUnionMethod2.two() = _ForceUnionMethod2; +} + @Freezed(toJson: false) class OptionalToJson with _$OptionalToJson { factory OptionalToJson() = _OptionalToJson; diff --git a/packages/freezed/test/json_test.dart b/packages/freezed/test/json_test.dart index ef216c31..f483f975 100644 --- a/packages/freezed/test/json_test.dart +++ b/packages/freezed/test/json_test.dart @@ -40,33 +40,6 @@ Future main() async { ); }); - test('Do not generate when/map even if fromJson is present', () async { - await expectLater( - compile(r''' -import 'json.dart'; - -void main() { - final a = NoWhen(); -} -'''), - completes, - ); - - await expectLater( - compile(r''' -import 'json.dart'; -import 'json.dart'; - -void main() { - final a = NoWhen(); - a.whenOrNull(); - a.mapOrNull(); -} -'''), - throwsCompileError, - ); - }); - group('Freezed.unionKey', () { test('fromJson', () { expect( diff --git a/packages/freezed/test/map_test.dart b/packages/freezed/test/map_test.dart new file mode 100644 index 00000000..9c922f1f --- /dev/null +++ b/packages/freezed/test/map_test.dart @@ -0,0 +1,415 @@ +// ignore_for_file: prefer_const_constructors, omit_local_variable_types + +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:build_test/build_test.dart'; +import 'package:test/test.dart'; + +import 'common.dart'; +import 'integration/multiple_constructors.dart'; + +void main() { + group('map', () { + test('works with no default ctr', () { + var value = NoDefault.first('a'); + + expect( + value.map( + first: (NoDefault1 value) => '${value.a} first', + second: (NoDefault2 value) => throw Error(), + ), + 'a first', + ); + + value = NoDefault.second('a'); + + expect( + value.map( + first: (NoDefault1 value) => throw Error(), + second: (NoDefault2 value) => '${value.a} second', + ), + 'a second', + ); + }); + + group('default ctor', () { + test("assert callbacks can't be null", () async { + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest('a'); + + value.map( + (SwitchTest0 a) {}, + first: (SwitchTest1 b) {}, + second: (SwitchTest2 c) {}, + ); +} +'''), completes); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest('a'); + + value.map( + (SwitchTest0 a) {}, + first: null, + second: (SwitchTest2 value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest('a'); + + value.map( + null, + first: (SwitchTest1 value) {}, + second: (SwitchTest2 value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest('a'); + + value.map( + (SwitchTest0 a) {}, + first: (SwitchTest1 value) {}, + second: null, + ); +} +'''), throwsCompileError); + }); + test('calls default callback', () { + final value = SwitchTest('a'); + + expect( + value.map( + (SwitchTest0 value) => '${value.a} 42', + first: (SwitchTest1 value) => throw Error(), + second: (SwitchTest2 value) => throw Error(), + ), + 'a 42', + ); + }); + }); + + group('first ctor', () { + test("assert callbacks can't be null", () async { + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a'); + + value.map( + (SwitchTest0 a) {}, + first: (SwitchTest1 b) {}, + second: (SwitchTest2 c) {}, + ); +} +'''), completes); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a'); + + value.map( + (SwitchTest0 a) {}, + first: null, + second: (SwitchTest2 value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a'); + + value.map( + null, + first: (SwitchTest1 value) {}, + second: (SwitchTest2 value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a'); + + value.map( + (SwitchTest0 a) {}, + first: (SwitchTest1 value) {}, + second: null, + ); +} +'''), throwsCompileError); + }); + + test('calls first callback', () { + final value = SwitchTest.first('a', b: false, d: .42); + + expect( + value.map( + (SwitchTest0 a) => throw Error(), + first: (SwitchTest1 value) => '${value.a} ${value.b} ${value.d}', + second: (SwitchTest2 value) => throw Error(), + ), + 'a false 0.42', + ); + }); + }); + group('second ctor', () { + test("assert callbacks can't be null", () async { + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.second('a'); + + value.map( + (SwitchTest0 a) {}, + first: (SwitchTest1 b) {}, + second: (SwitchTest2 c) {}, + ); +} +'''), completes); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.second('a'); + + value.map( + (SwitchTest0 a) {}, + first: null, + second: (SwitchTest2 value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.second('a'); + + value.map( + null, + first: (SwitchTest1 value) {}, + second: (SwitchTest2 value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.second('a'); + + value.map( + (SwitchTest0 a) {}, + first: (SwitchTest1 value) {}, + second: null, + ); +} +'''), throwsCompileError); + }); + test('calls second callback', () { + final value = SwitchTest.second('a', 21, .42); + + expect( + value.map( + (SwitchTest0 a) => throw Error(), + first: (SwitchTest1 value) => throw Error(), + second: (SwitchTest2 value) => '${value.a} ${value.c} ${value.d}', + ), + 'a 21 0.42', + ); + }); + }); + + test('named parameters are marked as required', () async { + final main = await resolveSources( + { + 'freezed|test/integration/main.dart': r''' +library main; +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a', b: false, d: .42); + + value.map( + (a) => 42, + ); +} +''', + }, + (r) => r.findLibraryByName('main'), + ); + + final errorResult = await main!.session + .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + + expect(errorResult.errors, isNotEmpty); + }); + }); + + group('maybeMap', () { + test('returns callback result if has callback', () { + var value = SwitchTest('a'); + + expect( + value.maybeMap( + (value) => '${value.a} default', + orElse: () => throw Error(), + ), + 'a default', + ); + + value = SwitchTest.first('a', b: false, d: .42); + + expect( + value.maybeMap( + null, + first: (SwitchTest1 value) => '${value.a} ${value.b} ${value.d}', + orElse: () => throw Error(), + ), + 'a false 0.42', + ); + + value = SwitchTest.second('a', 21, 0.42); + + expect( + value.maybeMap( + null, + second: (SwitchTest2 value) => '${value.a} ${value.c} ${value.d}', + orElse: () => throw Error(), + ), + 'a 21 0.42', + ); + }); + + test('assert orElse is passed', () async { + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + var value = SwitchTest('a'); + + value.maybeMap( + (SwitchTest0 a) {}, + orElse: () {}, + ); +} +'''), completes); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + var value = SwitchTest('a'); + + value.maybeMap( + (SwitchTest0 a) => '$a default', + orElse: null, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + var value = SwitchTest('a'); + + value.maybeMap( + (SwitchTest0 a) => '$a default', + orElse: null, + ); +} +'''), throwsCompileError); + }); + + test('orElse is called', () { + var value = SwitchTest('a'); + + expect(value.maybeMap(null, orElse: () => 42), 42); + + value = SwitchTest.first('a', b: false, d: .42); + + expect(value.maybeMap(null, orElse: () => 42), 42); + + value = SwitchTest.second('a', 21, 0.42); + + expect(value.maybeMap(null, orElse: () => 42), 42); + }); + + test('orElse is required', () async { + final main = await resolveSources( + { + 'freezed|test/integration/main.dart': r''' +library main; +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest('a'); + + value.maybeMap(null); +} +''', + }, + (r) => r.findLibraryByName('main'), + ); + + final errorResult = await main!.session + .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + + expect(errorResult.errors, isNotEmpty); + }); + }); + + group('mapOrNull', () { + test('has all parameters as optional', () { + expect(NoDefault.first('a').mapOrNull(), null); + expect(NoDefault.second('a').mapOrNull(), null); + }); + + test('can map to nullable return type without type cast', () { + String? res = NoDefault.first('a').mapOrNull( + first: (value) => value.a.isEmpty ? null : value.a, + ); + expect(res, 'a'); + }); + + test('calls callback on matching constructor', () { + expect( + NoDefault.first('a').mapOrNull(first: (v) => v), + NoDefault.first('a'), + ); + + expect( + NoDefault.second('a').mapOrNull(second: (v) => v), + NoDefault.second('a'), + ); + }); + }); +} diff --git a/packages/freezed/test/multiple_constructors_test.dart b/packages/freezed/test/multiple_constructors_test.dart index ddb6fdb7..8797c9bf 100644 --- a/packages/freezed/test/multiple_constructors_test.dart +++ b/packages/freezed/test/multiple_constructors_test.dart @@ -231,6 +231,177 @@ void main() { }, ); + test('when works on unnamed constructors', () { + expect(RequiredParams(a: 'a').when((a) => 21, second: (_) => 42), 21); + expect( + RequiredParams.second(a: 'a').when((a) => 21, second: (_) => 42), + 42, + ); + }); + + test('whenOrNull works on unnamed constructors', () { + expect( + RequiredParams(a: 'a').whenOrNull((a) => 21, second: (_) => 42), + 21, + ); + expect( + RequiredParams.second(a: 'a').whenOrNull( + (a) => 21, + second: (_) => 42, + ), + 42, + ); + + expect( + RequiredParams(a: 'a').whenOrNull(null, second: (_) => 42), + null, + ); + expect( + RequiredParams.second(a: 'a').whenOrNull((a) => 21), + null, + ); + }); + + test('map works on unnamed constructors', () { + expect(RequiredParams(a: 'a').map((a) => 21, second: (_) => 42), 21); + expect( + RequiredParams.second(a: 'a').map((a) => 21, second: (_) => 42), + 42, + ); + }); + + test('mapOrNull works on unnamed constructors', () { + expect(RequiredParams(a: 'a').mapOrNull((a) => 21), 21); + expect( + RequiredParams.second(a: 'a').mapOrNull((a) => 21, second: (_) => 42), + 42, + ); + + expect(RequiredParams(a: 'a').mapOrNull(null), null); + expect( + RequiredParams.second(a: 'a').mapOrNull(null), + null, + ); + }); + + test('maybeMap works on unnamed constructors', () { + expect(RequiredParams(a: 'a').maybeMap((a) => 21, orElse: () => 42), 21); + expect( + RequiredParams.second(a: 'a').maybeMap((a) => 21, orElse: () => 42), + 42, + ); + expect(RequiredParams(a: 'a').maybeMap(null, orElse: () => 42), 42); + expect( + RequiredParams.second(a: 'a').maybeMap(null, orElse: () => 42), + 42, + ); + }); + + test('maybeWhen works on unnamed constructors', () { + expect(RequiredParams(a: 'a').maybeWhen((a) => 21, orElse: () => 42), 21); + expect( + RequiredParams.second(a: 'a').maybeWhen((a) => 21, orElse: () => 42), + 42, + ); + expect(RequiredParams(a: 'a').maybeWhen(null, orElse: () => 42), 42); + expect( + RequiredParams.second(a: 'a').maybeWhen(null, orElse: () => 42), + 42, + ); + }); + + test('maybeMap can use FutureOr', () async { + var res = NoDefault.first('a').maybeMap>( + first: (a) => 21, + orElse: () => Future.value(42), + ); + + expect(res, 21); + + res = NoDefault.second('a').maybeMap>( + second: (b) => Future.value(42), + orElse: () => 21, + ); + + await expectLater(res, completion(42)); + }); + + test('mapOrNull can use FutureOr', () async { + var res = NoDefault.first('a').mapOrNull>( + first: (a) => 21, + ); + + expect(res, 21); + + res = NoDefault.second('a').mapOrNull>( + second: (b) => Future.value(42), + ); + + await expectLater(res, completion(42)); + }); + + test('map can use FutureOr', () async { + var res = NoDefault.first('a').map>( + first: (a) => 21, + second: (b) => Future.value(42), + ); + + expect(res, 21); + + res = NoDefault.second('a').map>( + first: (a) => 21, + second: (b) => Future.value(42), + ); + + await expectLater(res, completion(42)); + }); + + test('maybeWhen can use FutureOr', () async { + var res = NoDefault.first('a').maybeWhen>( + first: (a) => 21, + orElse: () => Future.value(42), + ); + + expect(res, 21); + + res = NoDefault.second('a').maybeWhen>( + second: (b) => Future.value(42), + orElse: () => 21, + ); + + await expectLater(res, completion(42)); + }); + + test('whenOrNull can use FutureOr', () async { + var res = NoDefault.first('a').whenOrNull>( + first: (a) => 21, + ); + + expect(res, 21); + + res = NoDefault.second('a').whenOrNull>( + second: (b) => Future.value(42), + ); + + await expectLater(res, completion(42)); + }); + + test('when can use FutureOr', () async { + var res = NoDefault.first('a').when>( + first: (a) => 21, + second: (b) => Future.value(42), + ); + + expect(res, 21); + + res = NoDefault.second('a').when>( + first: (a) => 21, + second: (b) => Future.value(42), + ); + + await expectLater(res, completion(42)); + }); + test('redirected constructors do have public properties', () { final ctor0 = NoCommonParam0('a', b: 42); String a = ctor0.a; @@ -417,7 +588,6 @@ void main() { }, error); }); - group('NestedList', () { test('generates List of correct type', () async { final nestedListClass = _getClassElement('ShallowNestedList'); diff --git a/packages/freezed/test/optional_maybe_test.dart b/packages/freezed/test/optional_maybe_test.dart new file mode 100644 index 00000000..e3fc2c25 --- /dev/null +++ b/packages/freezed/test/optional_maybe_test.dart @@ -0,0 +1,137 @@ +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:build_test/build_test.dart'; +import 'package:test/test.dart'; + +import 'common.dart'; +import 'integration/optional_maybe.dart'; + +void main() { + test('has no issue', () async { + final main = await resolveSources( + { + 'freezed|test/integration/optional_maybe.dart': useAssetReader, + }, + (r) => r.libraries.firstWhere( + (element) => element.source.toString().contains('optional_maybe')), + ); + + final errorResult = await main.session + .getErrors('/freezed/test/integration/optional_maybe.freezed.dart') + as ErrorsResult; + + expect(errorResult.errors, isEmpty); + }); + + test('does not generates maybeMap', () async { + await expectLater(compile(r''' + import 'optional_maybe.dart'; + + void main() { + final value = OptionalMaybeMap.first(); + + value.maybeMap(orElse: () => null); + value.map( + first: (_) {}, + second: (_) {}, + ); + value.mapOrNull(); + } + '''), throwsCompileError); + + const OptionalMaybeMap.first() + ..whenOrNull() + ..maybeWhen(orElse: () {}) + ..when( + first: () {}, + second: () {}, + ); + }); + + test('does not generates maybeWhen', () async { + await expectLater(compile(r''' + import 'optional_maybe.dart'; + + void main() { + final value = OptionalMaybeWhen.first(); + + value.maybeWhen(orElse: () => null); + value.when( + first: () {}, + second: () {}, + ); + value.whenOrNull(); + } + '''), throwsCompileError); + + const OptionalMaybeWhen.first() + ..mapOrNull() + ..maybeMap(orElse: () {}) + ..map( + first: (_) {}, + second: (_) {}, + ); + }); + + test('can disable copyWith', () async { + await expectLater(compile(r''' +import 'optional_maybe.dart'; + +void main() { + OptionalCopyWith().copyWith; +} +'''), throwsCompileError); + }); + + test('can disable toString', () { + expect( + const OptionalToString().toString(), + r"Instance of '_OptionalToString'", + ); + }); + + test('can disable ==/hash', () { + expect( + OptionalEqual(), + isNot(OptionalEqual()), + ); + expect( + OptionalEqual().hashCode, + isNot(OptionalEqual().hashCode), + ); + }); + + test('can force the generation of when/map', () { + ForceUnionMethod2.two() + ..map(two: (_) {}) + ..mapOrNull() + ..maybeMap(orElse: () {}) + ..when(two: () {}) + ..whenOrNull() + ..maybeWhen(orElse: () {}); + + ForceUnionMethod() + ..map((value) => null) + ..mapOrNull((value) => null) + ..maybeMap((value) => null, orElse: () {}) + ..when(() => null) + ..whenOrNull(() => null) + ..maybeWhen(() => null, orElse: () {}); + }); + + test('can disable toJson', () async { + OptionalToJson(); + OptionalToJson.fromJson({}); + + await expectLater(compile(r''' +import 'optional_maybe.dart'; + +void main() { + OptionalToJson().toJson; +} +'''), throwsCompileError); + }); + + test('can force toJson', () async { + expect(ForceToJson(42).toJson(), {'a': 42}); + }); +} diff --git a/packages/freezed/test/options_test.dart b/packages/freezed/test/options_test.dart deleted file mode 100644 index a707bccf..00000000 --- a/packages/freezed/test/options_test.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:build_test/build_test.dart'; -import 'package:test/test.dart'; - -import 'common.dart'; -import 'integration/optional_maybe.dart'; - -void main() { - test('has no issue', () async { - final main = await resolveSources( - {'freezed|test/integration/optional_maybe.dart': useAssetReader}, - (r) => r.libraries.firstWhere( - (element) => element.source.toString().contains('optional_maybe'), - ), - ); - - final errorResult = await main.session.getErrors( - '/freezed/test/integration/optional_maybe_test.freezed.dart', - ) as ErrorsResult; - - expect(errorResult.errors, isEmpty); - }); - - test('can disable copyWith', () async { - await expectLater( - compile(r''' -import 'optional_maybe.dart'; - -void main() { - OptionalCopyWith().copyWith; -} -'''), - throwsCompileError, - ); - }); - - test('can disable toString', () { - expect( - const OptionalToString().toString(), - r"Instance of '_OptionalToString'", - ); - }); - - test('can disable ==/hash', () { - expect(OptionalEqual(), isNot(OptionalEqual())); - expect(OptionalEqual().hashCode, isNot(OptionalEqual().hashCode)); - }); - - test('can disable toJson', () async { - OptionalToJson(); - OptionalToJson.fromJson({}); - - await expectLater( - compile(r''' -import 'optional_maybe.dart'; - -void main() { - OptionalToJson().toJson; -} -'''), - throwsCompileError, - ); - }); - - test('can force toJson', () async { - expect(ForceToJson(42).toJson(), {'a': 42}); - }); -} diff --git a/packages/freezed/test/single_class_constructor_test.dart b/packages/freezed/test/single_class_constructor_test.dart index d9c28bd2..6bea7455 100644 --- a/packages/freezed/test/single_class_constructor_test.dart +++ b/packages/freezed/test/single_class_constructor_test.dart @@ -328,32 +328,6 @@ Future main() async { expect(didEqual, isFalse); }); - test('does not have when', () async { - await expectLater( - compile(r''' -import 'single_class_constructor.dart'; - -void main() { - MyClass().when; -} -'''), - throwsCompileError, - ); - }); - - test('does not have maybeWhen', () async { - await expectLater( - compile(r''' -import 'single_class_constructor.dart'; - -void main() { - MyClass().maybeWhen; -} -'''), - throwsCompileError, - ); - }); - test('regression 399', () async { await expectLater( compile(r''' @@ -384,32 +358,6 @@ void main() { ); }); - test('does not have map', () async { - await expectLater( - compile(r''' -import 'single_class_constructor.dart'; - -void main() { - MyClass().map; -} -'''), - throwsCompileError, - ); - }); - - test('does not have maybeMap', () async { - await expectLater( - compile(r''' -import 'single_class_constructor.dart'; - -void main() { - MyClass().maybeMap; -} -'''), - throwsCompileError, - ); - }); - test('has no issue', () async { final singleClassLibrary = await analyze(); @@ -424,6 +372,42 @@ void main() { expect('${SingleNamedCtor.named(42)}', 'SingleNamedCtor.named(a: 42)'); }); + test('single-case union does have map', () async { + expect( + SingleNamedCtor.named(42).map( + named: (WhateverSingleNamedCtor value) => '${value.a}', + ), + '42', + ); + }); + + test('single-case union does have maybeMap', () async { + expect( + SingleNamedCtor.named(42).maybeMap( + named: (WhateverSingleNamedCtor value) => '${value.a}', + orElse: () => throw Exception('orElse called'), + ), + '42', + ); + }); + + test('single-case union does have when', () async { + expect( + SingleNamedCtor.named(42).when(named: (int value) => '$value'), + '42', + ); + }); + + test('single-case union does have maybeWhen', () async { + expect( + SingleNamedCtor.named(42).maybeWhen( + named: (int value) => '$value', + orElse: () => throw Exception('orElse called'), + ), + '42', + ); + }); + test('can be created as const', () { expect(identical(const MyClass(a: '42'), const MyClass(a: '42')), isTrue); }); diff --git a/packages/freezed/test/when_test.dart b/packages/freezed/test/when_test.dart new file mode 100644 index 00000000..c8c75e5b --- /dev/null +++ b/packages/freezed/test/when_test.dart @@ -0,0 +1,391 @@ +// ignore_for_file: prefer_const_constructors, omit_local_variable_types +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:build_test/build_test.dart'; +import 'package:test/test.dart'; + +import 'common.dart'; +import 'integration/multiple_constructors.dart'; + +void main() { + group('when', () { + test('works with no default ctr', () { + var value = NoDefault.first('a'); + + expect( + value.when( + first: (String a) => '$a first', + second: (String a) => throw Error(), + ), + 'a first', + ); + + value = NoDefault.second('a'); + + expect( + value.when( + first: (String a) => throw Error(), + second: (String a) => '$a second', + ), + 'a second', + ); + }); + group('default ctor', () { + test("assert callbacks can't be null", () async { + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest('a'); + + value.when( + (String a) {}, + first: (String a, bool? b, double? d) {}, + second: (String a, int? c, double? d) {}, + ); +} +'''), completes); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest('a'); + + value.when( + (String a) {}, + first: null, + second: (String value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest('a'); + + value.when( + null, + first: (String value) {}, + second: (String value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest('a'); + + value.when( + (String a) {}, + first: (String value) {}, + second: null, + ); +} +'''), throwsCompileError); + }); + + test('calls default callback', () { + final value = SwitchTest('a'); + + expect( + value.when( + (String a) => '$a 42', + first: (String a, bool? b, double? d) => throw Error(), + second: (String a, int? c, double? d) => throw Error(), + ), + 'a 42', + ); + }); + }); + + group('first ctor', () { + test("assert callbacks can't be null", () async { + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a'); + value.when( + (String a) {}, + first: (String a, bool? b, double? d) {}, + second: (String a, int? c, double? d) {}, + ); +} +'''), completes); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a'); + + value.when( + (String a) {}, + first: null, + second: (String value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a'); + + value.when( + null, + first: (String value) {}, + second: (String value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a'); + + value.when( + (String a) {}, + first: (String value) {}, + second: null, + ); +} +'''), throwsCompileError); + }); + + test('calls first callback', () { + final value = SwitchTest.first('a', b: false, d: .42); + + expect( + value.when( + (String a) => throw Error(), + first: (String a, bool? b, double? d) => '$a $b $d', + second: (String a, int? c, double? d) => throw Error(), + ), + 'a false 0.42', + ); + }); + }); + + group('second ctor', () { + test("assert callbacks can't be null", () async { + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.second('a'); + value.when( + (String a) {}, + first: (String a, bool? b, double? d) {}, + second: (String a, int? c, double? d) {}, + ); +} +'''), completes); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.second('a'); + + value.when( + (String a) {}, + first: null, + second: (String value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.second('a'); + + value.when( + null, + first: (String value) {}, + second: (String value) {}, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.second('a'); + + value.when( + (String a) {}, + first: (String value) {}, + second: null, + ); +} +'''), throwsCompileError); + }); + + test('calls second callback', () { + final value = SwitchTest.second('a', 21, .42); + + expect( + value.when( + (String a) => throw Error(), + first: (String a, bool? b, double? d) => throw Error(), + second: (String a, int? c, double? d) => '$a $c $d', + ), + 'a 21 0.42', + ); + }); + }); + + test('named parameters are marked as required', () async { + final main = await resolveSources( + { + 'freezed|test/integration/main.dart': r''' +library main; +import 'multiple_constructors.dart'; + +void main() { + final value = SwitchTest.first('a', b: false, d: .42); + + value.when( + (String a) => 42, + ); +} +''', + }, + (r) => r.findLibraryByName('main'), + ); + + final errorResult = await main!.session + .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + + expect(errorResult.errors, isNotEmpty); + }); + }); + + group('maybeWhen', () { + test('returns callback result if has callback', () { + var value = SwitchTest('a'); + + expect( + value.maybeWhen( + (String a) => '$a default', + orElse: () => throw Error(), + ), + 'a default', + ); + + value = SwitchTest.first('a', b: false, d: .42); + + expect( + value.maybeWhen( + null, + first: (a, b, d) => '$a $b $d', + orElse: () => throw Error(), + ), + 'a false 0.42', + ); + + value = SwitchTest.second('a', 21, 0.42); + + expect( + value.maybeWhen( + null, + second: (a, c, d) => '$a $c $d', + orElse: () => throw Error(), + ), + 'a 21 0.42', + ); + }); + + test('assert orElse is passed', () async { + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + var value = SwitchTest('a'); + + value.maybeWhen( + (String a) {}, + orElse: () {}, + ); +} +'''), completes); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + var value = SwitchTest('a'); + + value.maybeWhen( + (String a) => '$a default', + orElse: null, + ); +} +'''), throwsCompileError); + + await expectLater(compile(r''' +import 'multiple_constructors.dart'; + +void main() { + var value = SwitchTest('a'); + + value.maybeWhen( + (String a) => '$a default', + orElse: null, + ); +} +'''), throwsCompileError); + }); + + test('orElse is called', () { + var value = SwitchTest('a'); + + expect(value.maybeWhen(null, orElse: () => 42), 42); + + value = SwitchTest.first('a', b: false, d: .42); + + expect(value.maybeWhen(null, orElse: () => 42), 42); + + value = SwitchTest.second('a', 21, 0.42); + + expect(value.maybeWhen(null, orElse: () => 42), 42); + }); + }); + + group('whenOrNull', () { + test('can map to nullable return type without type cast', () { + String? res = NoDefault.first('a').whenOrNull( + first: (a) => a.isEmpty ? null : a, + ); + expect(res, 'a'); + }); + + test('has all parameters as optional', () { + expect(NoDefault.first('a').whenOrNull(), null); + expect(NoDefault.second('a').whenOrNull(), null); + }); + + test('calls callback on matching constructor', () { + expect( + NoDefault.first('a').whenOrNull(first: (v) => v), + 'a', + ); + + expect( + NoDefault.second('a').whenOrNull(second: (v) => v), + 'a', + ); + }); + }); +} diff --git a/packages/freezed_annotation/CHANGELOG.md b/packages/freezed_annotation/CHANGELOG.md index 3209d366..eb9c780d 100644 --- a/packages/freezed_annotation/CHANGELOG.md +++ b/packages/freezed_annotation/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased minor + +Added `when`/`map` back + ## 3.0.0 - 2025-02-25 - **Breaking** removed `when`/`map` related options diff --git a/packages/freezed_annotation/lib/freezed_annotation.dart b/packages/freezed_annotation/lib/freezed_annotation.dart index f8ff7165..9a5f4631 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.dart @@ -62,6 +62,139 @@ class EqualUnmodifiableMapView int get hashCode => Object.hash(runtimeType, _source); } +/// Options for enabling/disabling specific `Union.map` features; +@JsonSerializable( + fieldRename: FieldRename.snake, + createToJson: false, + anyMap: true, +) +class FreezedMapOptions { + /// Options for enabling/disabling specific `Union.map` features; + const FreezedMapOptions({this.map, this.mapOrNull, this.maybeMap}); + + /// Decode a [FreezedMapOptions] from a build.yaml + factory FreezedMapOptions.fromJson(Map json) => + _$FreezedMapOptionsFromJson(json); + + /// Enables the generation of all `Union.map` features + static const all = + FreezedMapOptions(map: true, mapOrNull: true, maybeMap: true); + + /// Disables the generation of all `Union.map` features + static const none = + FreezedMapOptions(map: false, mapOrNull: false, maybeMap: false); + + /// Whether to generate `Union.map` + /// + /// If null, will fallback to the build.yaml configs + /// If that value is null too, defaults to true. + final bool? map; + + /// Whether to generate `Union.mapOrNull` + /// + /// If null, will fallback to the build.yaml configs + /// If that value is null too, defaults to true. + final bool? mapOrNull; + + /// Whether to generate `Union.maybeMap` + /// + /// If null, will fallback to the build.yaml configs + /// If that value is null too, defaults to true. + final bool? maybeMap; +} + +/// Options for enabling/disabling specific `Union.when` features; +@JsonSerializable( + fieldRename: FieldRename.snake, + createToJson: false, + anyMap: true, +) +class FreezedWhenOptions { + /// Options for enabling/disabling specific `Union.when` features; + const FreezedWhenOptions({ + this.when, + this.whenOrNull, + this.maybeWhen, + }); + + /// Decode a [FreezedWhenOptions] from a build.yaml + factory FreezedWhenOptions.fromJson(Map json) => + _$FreezedWhenOptionsFromJson(json); + + /// Enables the generation of all `Union.when` features + static const all = + FreezedWhenOptions(when: true, whenOrNull: true, maybeWhen: true); + + /// Disables the generation of all `Union.when` features + static const none = FreezedWhenOptions( + when: false, + whenOrNull: false, + maybeWhen: false, + ); + + /// Whether to generate `Union.when` + /// + /// If null, will fallback to the build.yaml configs + /// If that value is null too, defaults to true. + final bool? when; + + /// Whether to generate `Union.whenOrNull` + /// + /// If null, will fallback to the build.yaml configs + /// If that value is null too, defaults to true. + final bool? whenOrNull; + + /// Whether to generate `Union.maybeWhen` + /// + /// If null, will fallback to the build.yaml configs. + /// If that value is null too, defaults to true. + final bool? maybeWhen; +} + +class _FreezedWhenOptionsConverter + implements JsonConverter { + const _FreezedWhenOptionsConverter(); + + @override + FreezedWhenOptions? fromJson(Object? json) { + if (json == true) return FreezedWhenOptions.all; + if (json == false) return FreezedWhenOptions.none; + if (json == null) return null; + if (json is Map) return FreezedWhenOptions.fromJson(json); + + throw ArgumentError.value( + json, + 'json', + 'Expected a bool a Map, got ${json.runtimeType}', + ); + } + + @override + Object? toJson(FreezedWhenOptions? object) => null; +} + +class _FreezedMapOptionsConverter + implements JsonConverter { + const _FreezedMapOptionsConverter(); + + @override + FreezedMapOptions? fromJson(Object? json) { + if (json == true) return FreezedMapOptions.all; + if (json == false) return FreezedMapOptions.none; + if (json == null) return null; + if (json is Map) return FreezedMapOptions.fromJson(json); + + throw ArgumentError.value( + json, + 'json', + 'Expected a bool or a Map, got ${json.runtimeType}', + ); + } + + @override + Object? toJson(FreezedMapOptions? object) => throw UnimplementedError(); +} + /// {@template freezed_annotation.freezed} /// Flags a class as needing to be processed by Freezed and allows passing options. /// {@endtemplate} @@ -81,6 +214,8 @@ class Freezed { this.toStringOverride, this.fromJson, this.toJson, + this.map, + this.when, this.makeCollectionsUnmodifiable, this.addImplicitFinal = true, this.genericArgumentFactories = false, @@ -340,6 +475,20 @@ class Freezed { /// } /// ``` final bool genericArgumentFactories; + + /// Options for customizing the generation of `map` functions + /// + /// If null, picks up the default values from the project's `build.yaml`. + /// If that value is null too, defaults to [FreezedMapOptions.all]. + @_FreezedMapOptionsConverter() + final FreezedMapOptions? map; + + /// Options for customizing the generation of `when` functions + /// + /// If null, picks up the default values from the project's `build.yaml` + /// If that value is null too, defaults to [FreezedWhenOptions.all]. + @_FreezedWhenOptionsConverter() + final FreezedWhenOptions? when; } /// Defines an immutable data-class. diff --git a/packages/freezed_annotation/lib/freezed_annotation.g.dart b/packages/freezed_annotation/lib/freezed_annotation.g.dart index 5e609391..d937ed04 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.g.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.g.dart @@ -8,6 +8,18 @@ part of 'freezed_annotation.dart'; // JsonSerializableGenerator // ************************************************************************** +FreezedMapOptions _$FreezedMapOptionsFromJson(Map json) => FreezedMapOptions( + map: json['map'] as bool?, + mapOrNull: json['map_or_null'] as bool?, + maybeMap: json['maybe_map'] as bool?, + ); + +FreezedWhenOptions _$FreezedWhenOptionsFromJson(Map json) => FreezedWhenOptions( + when: json['when'] as bool?, + whenOrNull: json['when_or_null'] as bool?, + maybeWhen: json['maybe_when'] as bool?, + ); + Freezed _$FreezedFromJson(Map json) => Freezed( unionKey: json['union_key'] as String? ?? 'runtimeType', unionValueCase: $enumDecodeNullable( @@ -18,6 +30,8 @@ Freezed _$FreezedFromJson(Map json) => Freezed( toStringOverride: json['to_string_override'] as bool?, fromJson: json['from_json'] as bool?, toJson: json['to_json'] as bool?, + map: const _FreezedMapOptionsConverter().fromJson(json['map']), + when: const _FreezedWhenOptionsConverter().fromJson(json['when']), makeCollectionsUnmodifiable: json['make_collections_unmodifiable'] as bool? ?? true, addImplicitFinal: json['add_implicit_final'] as bool? ?? true, diff --git a/packages/freezed_annotation/test/freezed_test.dart b/packages/freezed_annotation/test/freezed_test.dart index 3f8b3b42..f2232705 100644 --- a/packages/freezed_annotation/test/freezed_test.dart +++ b/packages/freezed_annotation/test/freezed_test.dart @@ -2,6 +2,140 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:test/test.dart'; void main() { + group('FreezedMapOptions', () { + test('.fromJson', () { + expect( + FreezedMapOptions.fromJson({'map': false}).map, + isFalse, + ); + expect( + FreezedMapOptions.fromJson({'map': false}).mapOrNull, + isNull, + ); + expect( + FreezedMapOptions.fromJson({'map': false}).maybeMap, + isNull, + ); + + expect( + FreezedMapOptions.fromJson({'map_or_null': false}) + .map, + isNull, + ); + expect( + FreezedMapOptions.fromJson({'map_or_null': false}) + .mapOrNull, + isFalse, + ); + expect( + FreezedMapOptions.fromJson({'map_or_null': false}) + .maybeMap, + isNull, + ); + + expect( + FreezedMapOptions.fromJson({'maybe_map': false}).map, + isNull, + ); + expect( + FreezedMapOptions.fromJson({'maybe_map': false}) + .mapOrNull, + isNull, + ); + expect( + FreezedMapOptions.fromJson({'maybe_map': false}) + .maybeMap, + isFalse, + ); + }); + + test('.all', () { + expect(FreezedMapOptions.all.map, isTrue); + expect(FreezedMapOptions.all.maybeMap, isTrue); + expect(FreezedMapOptions.all.mapOrNull, isTrue); + }); + + test('.none', () { + expect(FreezedMapOptions.none.map, isFalse); + expect(FreezedMapOptions.none.maybeMap, isFalse); + expect(FreezedMapOptions.none.mapOrNull, isFalse); + }); + + test('()', () { + expect(const FreezedMapOptions().map, isNull); + expect(const FreezedMapOptions().maybeMap, isNull); + expect(const FreezedMapOptions().mapOrNull, isNull); + }); + }); + + group('FreezedWhenOptions', () { + test('.fromJson', () { + expect( + FreezedWhenOptions.fromJson({'when': false}).when, + isFalse, + ); + expect( + FreezedWhenOptions.fromJson({'when': false}) + .whenOrNull, + isNull, + ); + expect( + FreezedWhenOptions.fromJson({'when': false}) + .maybeWhen, + isNull, + ); + + expect( + FreezedWhenOptions.fromJson({'when_or_null': false}) + .when, + isNull, + ); + expect( + FreezedWhenOptions.fromJson({'when_or_null': false}) + .whenOrNull, + isFalse, + ); + expect( + FreezedWhenOptions.fromJson({'when_or_null': false}) + .maybeWhen, + isNull, + ); + + expect( + FreezedWhenOptions.fromJson({'maybe_when': false}) + .when, + isNull); + expect( + FreezedWhenOptions.fromJson({'maybe_when': false}) + .whenOrNull, + isNull, + ); + expect( + FreezedWhenOptions.fromJson({'maybe_when': false}) + .maybeWhen, + isFalse, + ); + }); + + test('.all', () { + expect(FreezedWhenOptions.all.when, isTrue); + expect(FreezedWhenOptions.all.maybeWhen, isTrue); + expect(FreezedWhenOptions.all.whenOrNull, isTrue); + }); + + test('.none', () { + expect(FreezedWhenOptions.none.when, isFalse); + expect(FreezedWhenOptions.none.maybeWhen, isFalse); + expect(FreezedWhenOptions.none.whenOrNull, isFalse); + }); + + test('()', () { + expect(const FreezedWhenOptions().when, isNull); + expect(const FreezedWhenOptions().maybeWhen, isNull); + expect(const FreezedWhenOptions().whenOrNull, isNull); + }); + }); + group('Freezed', () { test('.fromJson', () { final defaultValue = Freezed.fromJson({}); @@ -10,12 +144,86 @@ void main() { expect(defaultValue.equal, isNull); expect(defaultValue.fallbackUnion, null); expect(defaultValue.fromJson, isNull); + expect(defaultValue.map, isNull); expect(defaultValue.toJson, isNull); expect(defaultValue.toStringOverride, isNull); expect(defaultValue.unionKey, 'runtimeType'); expect(defaultValue.unionValueCase, isNull); + expect(defaultValue.when, isNull); expect(defaultValue.makeCollectionsUnmodifiable, isTrue); }); + + test('.fromJson({map: x})', () { + expect(Freezed.fromJson({'map': false}).map, FreezedMapOptions.none); + expect(Freezed.fromJson({'map': true}).map, FreezedMapOptions.all); + + expect( + Freezed.fromJson({ + 'map': { + 'map': true, + 'maybe_map': false, + 'map_or_null': true, + } + }).map, + isA() + .having( + (e) => e.map, + 'map', + isTrue, + ) + .having( + (e) => e.maybeMap, + 'maybeMap', + isFalse, + ) + .having( + (e) => e.mapOrNull, + 'mapOrNull', + isTrue, + ), + ); + + expect( + () => Freezed.fromJson({'map': 42}), + throwsA(isA()), + ); + }); + + test('.fromJson({when: x})', () { + expect(Freezed.fromJson({'when': false}).when, FreezedWhenOptions.none); + expect(Freezed.fromJson({'when': true}).when, FreezedWhenOptions.all); + + expect( + Freezed.fromJson({ + 'when': { + 'when': true, + 'maybe_when': false, + 'when_or_null': true, + } + }).when, + isA() + .having( + (e) => e.when, + 'when', + isTrue, + ) + .having( + (e) => e.maybeWhen, + 'maybeWhen', + isFalse, + ) + .having( + (e) => e.whenOrNull, + 'whenOrNull', + isTrue, + ), + ); + + expect( + () => Freezed.fromJson({'when': 42}), + throwsA(isA()), + ); + }); }); test('unfreezed', () {