From d24fdf2dcc1d8547398476a7eee288810d2513f0 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 6 Dec 2022 19:24:50 +0100 Subject: [PATCH] Fixes various copyWith issues where the generated code was invalid (#817) fixes #814 fixes #811 --- packages/_internal/lib/models.freezed.dart | 14 +- packages/_internal/pubspec.lock | 165 ++++++------------ .../freezed/lib/src/freezed_generator.dart | 66 +++++-- packages/freezed/lib/src/models.freezed.dart | 14 +- .../freezed/lib/src/templates/copy_with.dart | 23 ++- packages/freezed/test/common_types_test.dart | 2 +- .../test/integration/common_types.dart | 23 +++ .../integration/multiple_constructors.dart | 11 +- 8 files changed, 189 insertions(+), 129 deletions(-) diff --git a/packages/_internal/lib/models.freezed.dart b/packages/_internal/lib/models.freezed.dart index e182134d..31b89d28 100644 --- a/packages/_internal/lib/models.freezed.dart +++ b/packages/_internal/lib/models.freezed.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'models.dart'; @@ -537,6 +537,8 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _impliedProperties; @override List get impliedProperties { + if (_impliedProperties is EqualUnmodifiableListView) + return _impliedProperties; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_impliedProperties); } @@ -554,6 +556,7 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _withDecorators; @override List get withDecorators { + if (_withDecorators is EqualUnmodifiableListView) return _withDecorators; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_withDecorators); } @@ -561,6 +564,8 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _implementsDecorators; @override List get implementsDecorators { + if (_implementsDecorators is EqualUnmodifiableListView) + return _implementsDecorators; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_implementsDecorators); } @@ -568,6 +573,7 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _decorators; @override List get decorators { + if (_decorators is EqualUnmodifiableListView) return _decorators; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_decorators); } @@ -575,6 +581,8 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _cloneableProperties; @override List get cloneableProperties { + if (_cloneableProperties is EqualUnmodifiableListView) + return _cloneableProperties; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_cloneableProperties); } @@ -582,6 +590,7 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _asserts; @override List get asserts { + if (_asserts is EqualUnmodifiableListView) return _asserts; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_asserts); } @@ -1361,6 +1370,8 @@ class _$_Data implements _Data { final List _concretePropertiesName; @override List get concretePropertiesName { + if (_concretePropertiesName is EqualUnmodifiableListView) + return _concretePropertiesName; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_concretePropertiesName); } @@ -1368,6 +1379,7 @@ class _$_Data implements _Data { final List _constructors; @override List get constructors { + if (_constructors is EqualUnmodifiableListView) return _constructors; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_constructors); } diff --git a/packages/_internal/pubspec.lock b/packages/_internal/pubspec.lock index 2ea6296a..3b92292a 100644 --- a/packages/_internal/pubspec.lock +++ b/packages/_internal/pubspec.lock @@ -5,425 +5,372 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "3444216bfd127af50bbe4862d8843ed44db946dd933554f0d7285e89f10e28ac" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "50.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "68796c31f510c8455a06fed75fc97d8e5ad04d324a830322ab3efc9feb6201c1" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "5.2.0" args: dependency: transitive description: name: args - sha256: b003c3098049a51720352d219b0bb5f219b60fbfb68e7a4748139a06a5676515 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.3.1" async: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.10.0" build: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.3.1" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "7c35a3a7868626257d8aee47b51c26b9dba11eaddf3431117ed2744951416aab" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.1.0" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "6f48c61a9dcd2c3a9e62d3dcdab1ba382790e2f31026288cbabe55d6003c9c23" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.3.2" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "7.2.7" built_collection: dependency: transitive description: name: built_collection - sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - sha256: "59e08b0079bb75f7e27392498e26339387c1089c6bd58525a14eb8508637277b" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "8.4.2" checked_yaml: dependency: transitive description: name: checked_yaml - sha256: dd007e4fb8270916820a0d66e24f619266b60773cddd082c6439341645af2659 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.0.1" code_builder: dependency: transitive description: name: code_builder - sha256: "02ce3596b459c666530f045ad6f96209474e8fee6e4855940a3cee65fb872ec5" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "4.3.0" collection: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.17.0" convert: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "3.1.1" crypto: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "3.0.2" dart_style: dependency: transitive description: name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.2.4" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "6.1.4" fixnum: dependency: transitive description: name: fixnum - sha256: "04be3e934c52e082558cc9ee21f42f5c1cd7a1262f4c63cd0357c08d5bba81ec" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.0.1" freezed: dependency: "direct dev" description: name: freezed - sha256: "7070a65156b1d60b81649005843b9a6ec861db6903dec2db082c95473c8f5f72" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.3.0" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.2.0" frontend_server_client: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "3.2.0" glob: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.1.1" graphs: dependency: transitive description: name: graphs - sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.2.0" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "4.0.2" io: dependency: transitive description: name: io - sha256: "0d4c73c3653ab85bf696d51a9657604c900a370549196a91f33e4c39af760852" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.0.3" js: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "0.6.5" json_annotation: dependency: transitive description: name: json_annotation - sha256: "3520fa844009431b5d4491a5a778603520cdc399ab3406332dcc50f93547258c" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "4.7.0" logging: dependency: transitive description: name: logging - sha256: c0bbfe94d46aedf9b8b3e695cf3bd48c8e14b35e3b2c639e0aa7755d589ba946 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.1.0" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "0.12.13" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.8.0" mime: dependency: transitive description: name: mime - sha256: "52e38f7e1143ef39daf532117d6b8f8f617bf4bcd6044ed8c29040d20d269630" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.0.3" package_config: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.1.0" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.8.3" pool: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.5.1" pub_semver: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.1.3" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.2.1" shelf: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.4.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.0.3" source_gen: dependency: transitive description: name: source_gen - sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.2.6" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.1.1" stream_transform: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.2.1" timing: dependency: transitive description: name: timing - sha256: c386d07d7f5efc613479a7c4d9d64b03710b03cfaa7e8ad5f2bfb295a1f0dfad - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.0.0" typed_data: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.3.1" watcher: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "1.0.2" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "3a969ddcc204a3e34e863d204b29c0752716f78b6f9cc8235083208d268a4ccd" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "2.2.0" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" - url: "https://pub.dev" + url: "https://pub.dartlang.org" source: hosted version: "3.1.1" sdks: - dart: ">=2.18.0 <4.0.0" + dart: ">=2.18.0 <3.0.0" diff --git a/packages/freezed/lib/src/freezed_generator.dart b/packages/freezed/lib/src/freezed_generator.dart index e3ece343..7f73dd56 100644 --- a/packages/freezed/lib/src/freezed_generator.dart +++ b/packages/freezed/lib/src/freezed_generator.dart @@ -247,11 +247,14 @@ Read here: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/C in constructorsNeedsGeneration.first.parameters.allParameters) { final library = parameter.parameterElement!.library!; + // Whether a property was downcasted other than through a nullability change. + // Such as changing int -> num, but not int -> int? + var didNonNullDowncast = false; + var didDowncast = false; var anyMatchingPropertyIsFinal = parameter.isFinal; var commonTypeBetweenAllUnionConstructors = parameter.parameterElement!.type; - // skip(1) as "parameter" is from the first constructor. for (final constructor in constructorsNeedsGeneration) { final matchingParameter = constructor.parameters.allParameters .firstWhereOrNull((p) => p.name == parameter.name); @@ -259,14 +262,44 @@ Read here: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/C // be present in the abstract class. if (matchingParameter == null) continue parameterLoop; + final parameterType = matchingParameter.parameterElement!.type; + anyMatchingPropertyIsFinal = anyMatchingPropertyIsFinal || matchingParameter.isFinal; + final previousCommonType = commonTypeBetweenAllUnionConstructors; commonTypeBetweenAllUnionConstructors = library.typeSystem.leastUpperBound( commonTypeBetweenAllUnionConstructors, - matchingParameter.parameterElement!.type, + parameterType, ); + + // Checking the previous vs new common type isn't enough to determine + // whether a property was downcasted. + // Depending on the constructor order, the common type could already + // be low enough to apply too all properties. + // For example: + // factory Example.first(Object? a); + // factory Example.second(int a); + + // At the same time, we can't just check the current property type with + // the current common type, as when the common type changes, it may + // match the current property type. For example: + // factory Example.first(int a); + // factory Example.second(Object? a); + if (previousCommonType != commonTypeBetweenAllUnionConstructors || + parameterType != commonTypeBetweenAllUnionConstructors) { + didDowncast = true; + + final nonNullCommonType = library.typeSystem + .promoteToNonNull(commonTypeBetweenAllUnionConstructors); + + didNonNullDowncast = didNonNullDowncast || + (previousCommonType != commonTypeBetweenAllUnionConstructors && + previousCommonType != nonNullCommonType) || + (parameterType != commonTypeBetweenAllUnionConstructors && + parameterType != nonNullCommonType); + } } final commonTypeString = resolveFullTypeStringFrom( @@ -275,12 +308,21 @@ Read here: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/C withNullability: true, ); - final commonProperty = Property.fromParameter(parameter).copyWith( - isFinal: anyMatchingPropertyIsFinal || - // The field was downcasted because some union cases use a - // different type for that field. As such, there is no valid setter - parameter.type != commonTypeString, + final commonProperty = Property( + isFinal: anyMatchingPropertyIsFinal || didDowncast, type: commonTypeString, + isNullable: commonTypeBetweenAllUnionConstructors.isNullable, + isDartList: commonTypeBetweenAllUnionConstructors.isDartCoreList, + isDartMap: commonTypeBetweenAllUnionConstructors.isDartCoreMap, + isDartSet: commonTypeBetweenAllUnionConstructors.isDartCoreSet, + isPossiblyDartCollection: + commonTypeBetweenAllUnionConstructors.isPossiblyDartCollection, + name: parameter.name, + decorators: parameter.decorators, + defaultValueSource: parameter.defaultValueSource, + doc: parameter.doc, + // TODO support JsonKey + hasJsonKey: false, ); result.readableProperties.add(commonProperty); @@ -290,8 +332,7 @@ Read here: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/C // - int? b is not allowed because `null` is not compatible with the // first union case. // - num c is not allowed because num is not assignable int/double - if (parameter.type == commonProperty.type || - '${parameter.type}?' == commonProperty.type) { + if (!didNonNullDowncast) { result.cloneableProperties.add( // Let's not downcast copyWith parameters commonProperty.copyWith(type: parameter.type), @@ -468,8 +509,7 @@ Read here: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/C yield DeepCloneableProperty( name: parameter.name, type: type!, - nullable: - parameter.type.nullabilitySuffix == NullabilitySuffix.question, + nullable: parameter.type.isNullable, typeName: typeElement.name, genericParameters: GenericsParameterTemplate( (parameter.type as InterfaceType) @@ -488,7 +528,9 @@ Read here: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/C for (final cloneableProperty in constructors.first.cloneableProperties) { for (final commonProperty in commonProperties) { if (cloneableProperty.name == commonProperty.name) { - yield cloneableProperty; + yield cloneableProperty.copyWith( + nullable: commonProperty.isNullable, + ); } } } diff --git a/packages/freezed/lib/src/models.freezed.dart b/packages/freezed/lib/src/models.freezed.dart index e182134d..31b89d28 100644 --- a/packages/freezed/lib/src/models.freezed.dart +++ b/packages/freezed/lib/src/models.freezed.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'models.dart'; @@ -537,6 +537,8 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _impliedProperties; @override List get impliedProperties { + if (_impliedProperties is EqualUnmodifiableListView) + return _impliedProperties; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_impliedProperties); } @@ -554,6 +556,7 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _withDecorators; @override List get withDecorators { + if (_withDecorators is EqualUnmodifiableListView) return _withDecorators; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_withDecorators); } @@ -561,6 +564,8 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _implementsDecorators; @override List get implementsDecorators { + if (_implementsDecorators is EqualUnmodifiableListView) + return _implementsDecorators; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_implementsDecorators); } @@ -568,6 +573,7 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _decorators; @override List get decorators { + if (_decorators is EqualUnmodifiableListView) return _decorators; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_decorators); } @@ -575,6 +581,8 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _cloneableProperties; @override List get cloneableProperties { + if (_cloneableProperties is EqualUnmodifiableListView) + return _cloneableProperties; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_cloneableProperties); } @@ -582,6 +590,7 @@ class _$_ConstructorDetails extends _ConstructorDetails { final List _asserts; @override List get asserts { + if (_asserts is EqualUnmodifiableListView) return _asserts; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_asserts); } @@ -1361,6 +1370,8 @@ class _$_Data implements _Data { final List _concretePropertiesName; @override List get concretePropertiesName { + if (_concretePropertiesName is EqualUnmodifiableListView) + return _concretePropertiesName; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_concretePropertiesName); } @@ -1368,6 +1379,7 @@ class _$_Data implements _Data { final List _constructors; @override List get constructors { + if (_constructors is EqualUnmodifiableListView) return _constructors; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_constructors); } diff --git a/packages/freezed/lib/src/templates/copy_with.dart b/packages/freezed/lib/src/templates/copy_with.dart index d2211b0e..ac4b5515 100644 --- a/packages/freezed/lib/src/templates/copy_with.dart +++ b/packages/freezed/lib/src/templates/copy_with.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:freezed/src/templates/parameter_template.dart'; import 'package:freezed/src/templates/properties.dart'; @@ -303,14 +304,28 @@ $constructorParameters String get _implClassName => '_${_abstractClassName}Impl'; Iterable _deepCopyMethods({required bool isConcrete}) sync* { - final toGenerateProperties = parent == null + /// Get the list of deep cloneable properties that should be implemented. + /// This is for unions, to avoid overriding a property on the concrete + /// copyWith class. if the shared interface already defines it. But at the + /// same time, still define the properties that aren't part of the shared + /// interface. + /// Also, sometimes the shared interface uses a nullable property but the + /// concrete class doesn't. Such as with {FreezedClass a} | {FreezedClass? b} + /// In this case even though the shared interface already defines the property, + /// we need to override the copyWith on the concrete interface to override the + /// properties nullability. + final propertiesToOverride = parent == null ? deepCloneableProperties : deepCloneableProperties.where((property) { - return !parent!.deepCloneableProperties - .any((p) => p.name == property.name); + final superProperty = parent!.deepCloneableProperties + .firstWhereOrNull((p) => p.name == property.name); + + // Either the property wasn't defined in the super type, or it was downcasted + return superProperty == null || + superProperty.nullable && !property.nullable; }); - for (final cloneableProperty in toGenerateProperties) { + for (final cloneableProperty in propertiesToOverride) { final earlyReturn = cloneableProperty.nullable ? ''' if (_value.${cloneableProperty.name} == null) { diff --git a/packages/freezed/test/common_types_test.dart b/packages/freezed/test/common_types_test.dart index f99d987b..a38c8b83 100644 --- a/packages/freezed/test/common_types_test.dart +++ b/packages/freezed/test/common_types_test.dart @@ -86,7 +86,7 @@ void main() { }); test('Can clone properties with nullability difference', () { - const value = CommonSuperSubtype0( + const value = CommonSuperSubtype( nullabilityDifference: 42, typeDifference: 21, ); diff --git a/packages/freezed/test/integration/common_types.dart b/packages/freezed/test/integration/common_types.dart index ef0cc83d..72717f3e 100644 --- a/packages/freezed/test/integration/common_types.dart +++ b/packages/freezed/test/integration/common_types.dart @@ -2,6 +2,20 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'common_types.freezed.dart'; +@freezed +class UnionDeepCopy with _$UnionDeepCopy { + factory UnionDeepCopy.first(CommonSuperSubtype value) = _UnionWrapperFirst; + factory UnionDeepCopy.second(CommonSuperSubtype? value) = _UnionWrapperSecond; +} + +@freezed +class Check with _$Check { + factory Check.first({required dynamic value}) = _CheckFirst; // NOT OK + factory Check.second({required int value}) = _CheckSecond; // OK + factory Check.third({required double value}) = _CheckThird; // OK + factory Check.fourth({required dynamic value}) = _CheckFourth; // OK +} + @freezed class CommonSuperSubtype with _$CommonSuperSubtype { const factory CommonSuperSubtype({ @@ -29,3 +43,12 @@ class CommonUnfreezed with _$CommonUnfreezed { factory CommonUnfreezed.two({required num a, required double b}) = CommonUnfreezedTwo; } + +// Checking that the constructor order does not matter +@unfreezed +class CommonUnfreezed2 with _$CommonUnfreezed2 { + factory CommonUnfreezed2.two({required num a, required double b}) = + CommonUnfreezedTwo2; + factory CommonUnfreezed2.one({required int a, required double b}) = + CommonUnfreezedOne2; +} diff --git a/packages/freezed/test/integration/multiple_constructors.dart b/packages/freezed/test/integration/multiple_constructors.dart index 62b8b671..6c1a9ed2 100644 --- a/packages/freezed/test/integration/multiple_constructors.dart +++ b/packages/freezed/test/integration/multiple_constructors.dart @@ -121,7 +121,11 @@ class RequiredParams with _$RequiredParams { const factory RequiredParams.second({required String a}) = RequiredParams1; } -@freezed +@Freezed( + // We're relying twice on the generated freezed types, which isn't supported. + // This breaks copyWith as it thinks both union cases have `children` as type `List` + copyWith: false, +) class NestedList with _$NestedList { factory NestedList.shallow(List children) = ShallowNestedList; @@ -135,6 +139,11 @@ class NestedListItem with _$NestedListItem { InnerNestedListItem; } +@Freezed( + // We're relying twice on the generated freezed types, which isn't supported. + // This breaks copyWith as it thinks both union cases have `children` as type `List` + copyWith: false, +) @freezed class NestedMap with _$NestedMap { factory NestedMap.shallow(Map children) =