From 56b641f3a7fdcd95e19c5d002745949442f4b658 Mon Sep 17 00:00:00 2001 From: Hauke Jaeger Date: Sat, 31 Aug 2024 13:10:44 +0200 Subject: [PATCH 1/7] feat: add finalize parameter to Freezed config finalize: true allows marking generated classes final (or sealed if it would otherwise be an abstract class). This then causes the Dart analyzer to yield pattern_never_matches_value_type warnings when trying to match such classes against patterns that can never match. --- packages/_internal/lib/models.dart | 1 + packages/_internal/lib/models.freezed.dart | 37 ++++++-- .../freezed/lib/src/freezed_generator.dart | 26 ++++++ packages/freezed/lib/src/models.dart | 1 + packages/freezed/lib/src/models.freezed.dart | 37 ++++++-- .../lib/src/templates/concrete_template.dart | 4 +- packages/freezed/test/finalized_test.dart | 84 +++++++++++++++++++ .../freezed/test/integration/finalized.dart | 24 ++++++ packages/freezed/test/source_gen_src.dart | 27 ++++++ .../lib/freezed_annotation.dart | 41 +++++++++ .../lib/freezed_annotation.g.dart | 1 + 11 files changed, 267 insertions(+), 16 deletions(-) create mode 100644 packages/freezed/test/finalized_test.dart create mode 100644 packages/freezed/test/integration/finalized.dart diff --git a/packages/_internal/lib/models.dart b/packages/_internal/lib/models.dart index 7046bdf5..9a980fa0 100644 --- a/packages/_internal/lib/models.dart +++ b/packages/_internal/lib/models.dart @@ -102,6 +102,7 @@ class Data with _$Data { required GenericsParameterTemplate genericsParameterTemplate, required bool shouldUseExtends, required bool genericArgumentFactories, + required bool shouldMarkSealedOrFinal, }) = _Data; } diff --git a/packages/_internal/lib/models.freezed.dart b/packages/_internal/lib/models.freezed.dart index ac1ce9f4..29fecde3 100644 --- a/packages/_internal/lib/models.freezed.dart +++ b/packages/_internal/lib/models.freezed.dart @@ -1098,6 +1098,7 @@ mixin _$Data { throw _privateConstructorUsedError; bool get shouldUseExtends => throw _privateConstructorUsedError; bool get genericArgumentFactories => throw _privateConstructorUsedError; + bool get shouldMarkSealedOrFinal => throw _privateConstructorUsedError; /// Create a copy of Data /// with the given fields replaced by the non-null parameter values. @@ -1126,7 +1127,8 @@ abstract class $DataCopyWith<$Res> { GenericsDefinitionTemplate genericsDefinitionTemplate, GenericsParameterTemplate genericsParameterTemplate, bool shouldUseExtends, - bool genericArgumentFactories}); + bool genericArgumentFactories, + bool shouldMarkSealedOrFinal}); $MapConfigCopyWith<$Res> get map; $WhenConfigCopyWith<$Res> get when; @@ -1163,6 +1165,7 @@ class _$DataCopyWithImpl<$Res, $Val extends Data> Object? genericsParameterTemplate = null, Object? shouldUseExtends = null, Object? genericArgumentFactories = null, + Object? shouldMarkSealedOrFinal = null, }) { return _then(_value.copyWith( name: null == name @@ -1229,6 +1232,10 @@ class _$DataCopyWithImpl<$Res, $Val extends Data> ? _value.genericArgumentFactories : genericArgumentFactories // ignore: cast_nullable_to_non_nullable as bool, + shouldMarkSealedOrFinal: null == shouldMarkSealedOrFinal + ? _value.shouldMarkSealedOrFinal + : shouldMarkSealedOrFinal // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } @@ -1276,7 +1283,8 @@ abstract class _$$DataImplCopyWith<$Res> implements $DataCopyWith<$Res> { GenericsDefinitionTemplate genericsDefinitionTemplate, GenericsParameterTemplate genericsParameterTemplate, bool shouldUseExtends, - bool genericArgumentFactories}); + bool genericArgumentFactories, + bool shouldMarkSealedOrFinal}); @override $MapConfigCopyWith<$Res> get map; @@ -1312,6 +1320,7 @@ class __$$DataImplCopyWithImpl<$Res> Object? genericsParameterTemplate = null, Object? shouldUseExtends = null, Object? genericArgumentFactories = null, + Object? shouldMarkSealedOrFinal = null, }) { return _then(_$DataImpl( name: null == name @@ -1378,6 +1387,10 @@ class __$$DataImplCopyWithImpl<$Res> ? _value.genericArgumentFactories : genericArgumentFactories // ignore: cast_nullable_to_non_nullable as bool, + shouldMarkSealedOrFinal: null == shouldMarkSealedOrFinal + ? _value.shouldMarkSealedOrFinal + : shouldMarkSealedOrFinal // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -1401,7 +1414,8 @@ class _$DataImpl implements _Data { required this.genericsDefinitionTemplate, required this.genericsParameterTemplate, required this.shouldUseExtends, - required this.genericArgumentFactories}) + required this.genericArgumentFactories, + required this.shouldMarkSealedOrFinal}) : assert(constructors.isNotEmpty), _concretePropertiesName = concretePropertiesName, _constructors = constructors; @@ -1451,10 +1465,12 @@ class _$DataImpl implements _Data { final bool shouldUseExtends; @override final bool genericArgumentFactories; + @override + final bool shouldMarkSealedOrFinal; @override String toString() { - return 'Data(name: $name, unionKey: $unionKey, generateCopyWith: $generateCopyWith, generateEqual: $generateEqual, generateToString: $generateToString, map: $map, when: $when, generateFromJson: $generateFromJson, generateToJson: $generateToJson, makeCollectionsImmutable: $makeCollectionsImmutable, concretePropertiesName: $concretePropertiesName, constructors: $constructors, genericsDefinitionTemplate: $genericsDefinitionTemplate, genericsParameterTemplate: $genericsParameterTemplate, shouldUseExtends: $shouldUseExtends, genericArgumentFactories: $genericArgumentFactories)'; + return 'Data(name: $name, unionKey: $unionKey, generateCopyWith: $generateCopyWith, generateEqual: $generateEqual, generateToString: $generateToString, map: $map, when: $when, generateFromJson: $generateFromJson, generateToJson: $generateToJson, makeCollectionsImmutable: $makeCollectionsImmutable, concretePropertiesName: $concretePropertiesName, constructors: $constructors, genericsDefinitionTemplate: $genericsDefinitionTemplate, genericsParameterTemplate: $genericsParameterTemplate, shouldUseExtends: $shouldUseExtends, genericArgumentFactories: $genericArgumentFactories, shouldMarkSealedOrFinal: $shouldMarkSealedOrFinal)'; } @override @@ -1495,7 +1511,10 @@ class _$DataImpl implements _Data { other.shouldUseExtends == shouldUseExtends) && (identical( other.genericArgumentFactories, genericArgumentFactories) || - other.genericArgumentFactories == genericArgumentFactories)); + other.genericArgumentFactories == genericArgumentFactories) && + (identical( + other.shouldMarkSealedOrFinal, shouldMarkSealedOrFinal) || + other.shouldMarkSealedOrFinal == shouldMarkSealedOrFinal)); } @override @@ -1516,7 +1535,8 @@ class _$DataImpl implements _Data { genericsDefinitionTemplate, genericsParameterTemplate, shouldUseExtends, - genericArgumentFactories); + genericArgumentFactories, + shouldMarkSealedOrFinal); /// Create a copy of Data /// with the given fields replaced by the non-null parameter values. @@ -1544,7 +1564,8 @@ abstract class _Data implements Data { required final GenericsDefinitionTemplate genericsDefinitionTemplate, required final GenericsParameterTemplate genericsParameterTemplate, required final bool shouldUseExtends, - required final bool genericArgumentFactories}) = _$DataImpl; + required final bool genericArgumentFactories, + required final bool shouldMarkSealedOrFinal}) = _$DataImpl; @override String get name; @@ -1578,6 +1599,8 @@ abstract class _Data implements Data { bool get shouldUseExtends; @override bool get genericArgumentFactories; + @override + bool get shouldMarkSealedOrFinal; /// Create a copy of Data /// with the given fields replaced by the non-null parameter values. diff --git a/packages/freezed/lib/src/freezed_generator.dart b/packages/freezed/lib/src/freezed_generator.dart index b0e044b3..55003ba0 100644 --- a/packages/freezed/lib/src/freezed_generator.dart +++ b/packages/freezed/lib/src/freezed_generator.dart @@ -95,6 +95,10 @@ class FreezedGenerator extends ParserGenerator { _assertValidFieldUsage(field, shouldUseExtends: shouldUseExtends); } + if (configs.finalize) { + _assertValidFinalizedUsage(declaration); + } + final constructorsNeedsGeneration = await _parseConstructorsNeedsGeneration( buildStep, declaration, @@ -146,6 +150,7 @@ class FreezedGenerator extends ParserGenerator { genericsParameterTemplate: GenericsParameterTemplate.fromGenericElement( declaration.declaredElement!.typeParameters, ), + shouldMarkSealedOrFinal: configs.finalize, ); } @@ -220,6 +225,26 @@ class FreezedGenerator extends ParserGenerator { } } + void _assertValidFinalizedUsage(ClassDeclaration declaration) { + if (declaration.sealedKeyword == null) { + throw InvalidGenerationSourceError( + '@freezed classes configured with [finalize: true] must be sealed', + element: declaration.declaredElement, + ); + } + + final hasPrivateConstructor = declaration.constructors.any((ctor) { + return ctor.name?.lexeme == '_'; + }); + + if (!hasPrivateConstructor) { + throw InvalidGenerationSourceError( + '@freezed classes configured with [finalize: true] require a MyClass._() constructor', + element: declaration.declaredElement, + ); + } + } + Future _needsJsonSerializable( BuildStep buildStep, GlobalData globalData, @@ -598,6 +623,7 @@ class FreezedGenerator extends ParserGenerator { orElse: () => _buildYamlConfigs.fromJson, ), addImplicitFinal: annotation.getField('addImplicitFinal')!.toBoolValue()!, + finalize: annotation.getField('finalize')!.toBoolValue()!, map: annotation.decodeField( 'map', decode: (obj) { diff --git a/packages/freezed/lib/src/models.dart b/packages/freezed/lib/src/models.dart index 26a8ea58..38960a35 100644 --- a/packages/freezed/lib/src/models.dart +++ b/packages/freezed/lib/src/models.dart @@ -92,6 +92,7 @@ class Data with _$Data { required GenericsParameterTemplate genericsParameterTemplate, required bool shouldUseExtends, required bool genericArgumentFactories, + required bool shouldMarkSealedOrFinal, }) = _Data; } diff --git a/packages/freezed/lib/src/models.freezed.dart b/packages/freezed/lib/src/models.freezed.dart index b1c315f6..e82a62aa 100644 --- a/packages/freezed/lib/src/models.freezed.dart +++ b/packages/freezed/lib/src/models.freezed.dart @@ -1054,6 +1054,7 @@ mixin _$Data { throw _privateConstructorUsedError; bool get shouldUseExtends => throw _privateConstructorUsedError; bool get genericArgumentFactories => throw _privateConstructorUsedError; + bool get shouldMarkSealedOrFinal => throw _privateConstructorUsedError; @JsonKey(includeFromJson: false, includeToJson: false) $DataCopyWith get copyWith => throw _privateConstructorUsedError; @@ -1080,7 +1081,8 @@ abstract class $DataCopyWith<$Res> { GenericsDefinitionTemplate genericsDefinitionTemplate, GenericsParameterTemplate genericsParameterTemplate, bool shouldUseExtends, - bool genericArgumentFactories}); + bool genericArgumentFactories, + bool shouldMarkSealedOrFinal}); $MapConfigCopyWith<$Res> get map; $WhenConfigCopyWith<$Res> get when; @@ -1115,6 +1117,7 @@ class _$DataCopyWithImpl<$Res, $Val extends Data> Object? genericsParameterTemplate = null, Object? shouldUseExtends = null, Object? genericArgumentFactories = null, + Object? shouldMarkSealedOrFinal = null, }) { return _then(_value.copyWith( name: null == name @@ -1181,6 +1184,10 @@ class _$DataCopyWithImpl<$Res, $Val extends Data> ? _value.genericArgumentFactories : genericArgumentFactories // ignore: cast_nullable_to_non_nullable as bool, + shouldMarkSealedOrFinal: null == shouldMarkSealedOrFinal + ? _value.shouldMarkSealedOrFinal + : shouldMarkSealedOrFinal // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } @@ -1224,7 +1231,8 @@ abstract class _$$DataImplCopyWith<$Res> implements $DataCopyWith<$Res> { GenericsDefinitionTemplate genericsDefinitionTemplate, GenericsParameterTemplate genericsParameterTemplate, bool shouldUseExtends, - bool genericArgumentFactories}); + bool genericArgumentFactories, + bool shouldMarkSealedOrFinal}); @override $MapConfigCopyWith<$Res> get map; @@ -1258,6 +1266,7 @@ class __$$DataImplCopyWithImpl<$Res> Object? genericsParameterTemplate = null, Object? shouldUseExtends = null, Object? genericArgumentFactories = null, + Object? shouldMarkSealedOrFinal = null, }) { return _then(_$DataImpl( name: null == name @@ -1324,6 +1333,10 @@ class __$$DataImplCopyWithImpl<$Res> ? _value.genericArgumentFactories : genericArgumentFactories // ignore: cast_nullable_to_non_nullable as bool, + shouldMarkSealedOrFinal: null == shouldMarkSealedOrFinal + ? _value.shouldMarkSealedOrFinal + : shouldMarkSealedOrFinal // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -1347,7 +1360,8 @@ class _$DataImpl implements _Data { required this.genericsDefinitionTemplate, required this.genericsParameterTemplate, required this.shouldUseExtends, - required this.genericArgumentFactories}) + required this.genericArgumentFactories, + required this.shouldMarkSealedOrFinal}) : assert(constructors.isNotEmpty), _concretePropertiesName = concretePropertiesName, _constructors = constructors; @@ -1397,10 +1411,12 @@ class _$DataImpl implements _Data { final bool shouldUseExtends; @override final bool genericArgumentFactories; + @override + final bool shouldMarkSealedOrFinal; @override String toString() { - return 'Data(name: $name, unionKey: $unionKey, generateCopyWith: $generateCopyWith, generateEqual: $generateEqual, generateToString: $generateToString, map: $map, when: $when, generateFromJson: $generateFromJson, generateToJson: $generateToJson, makeCollectionsImmutable: $makeCollectionsImmutable, concretePropertiesName: $concretePropertiesName, constructors: $constructors, genericsDefinitionTemplate: $genericsDefinitionTemplate, genericsParameterTemplate: $genericsParameterTemplate, shouldUseExtends: $shouldUseExtends, genericArgumentFactories: $genericArgumentFactories)'; + return 'Data(name: $name, unionKey: $unionKey, generateCopyWith: $generateCopyWith, generateEqual: $generateEqual, generateToString: $generateToString, map: $map, when: $when, generateFromJson: $generateFromJson, generateToJson: $generateToJson, makeCollectionsImmutable: $makeCollectionsImmutable, concretePropertiesName: $concretePropertiesName, constructors: $constructors, genericsDefinitionTemplate: $genericsDefinitionTemplate, genericsParameterTemplate: $genericsParameterTemplate, shouldUseExtends: $shouldUseExtends, genericArgumentFactories: $genericArgumentFactories, shouldMarkSealedOrFinal: $shouldMarkSealedOrFinal)'; } @override @@ -1441,7 +1457,10 @@ class _$DataImpl implements _Data { other.shouldUseExtends == shouldUseExtends) && (identical( other.genericArgumentFactories, genericArgumentFactories) || - other.genericArgumentFactories == genericArgumentFactories)); + other.genericArgumentFactories == genericArgumentFactories) && + (identical( + other.shouldMarkSealedOrFinal, shouldMarkSealedOrFinal) || + other.shouldMarkSealedOrFinal == shouldMarkSealedOrFinal)); } @override @@ -1462,7 +1481,8 @@ class _$DataImpl implements _Data { genericsDefinitionTemplate, genericsParameterTemplate, shouldUseExtends, - genericArgumentFactories); + genericArgumentFactories, + shouldMarkSealedOrFinal); @JsonKey(includeFromJson: false, includeToJson: false) @override @@ -1488,7 +1508,8 @@ abstract class _Data implements Data { required final GenericsDefinitionTemplate genericsDefinitionTemplate, required final GenericsParameterTemplate genericsParameterTemplate, required final bool shouldUseExtends, - required final bool genericArgumentFactories}) = _$DataImpl; + required final bool genericArgumentFactories, + required final bool shouldMarkSealedOrFinal}) = _$DataImpl; @override String get name; @@ -1523,6 +1544,8 @@ abstract class _Data implements Data { @override bool get genericArgumentFactories; @override + bool get shouldMarkSealedOrFinal; + @override @JsonKey(includeFromJson: false, includeToJson: false) _$$DataImplCopyWith<_$DataImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/packages/freezed/lib/src/templates/concrete_template.dart b/packages/freezed/lib/src/templates/concrete_template.dart index 97ff6d62..9187a652 100644 --- a/packages/freezed/lib/src/templates/concrete_template.dart +++ b/packages/freezed/lib/src/templates/concrete_template.dart @@ -64,7 +64,7 @@ ${copyWith?.concreteImpl(constructor.parameters) ?? ''} /// @nodoc $jsonSerializable ${constructor.decorators.join('\n')} -class $concreteName${data.genericsDefinitionTemplate} $_concreteSuper { +${data.shouldMarkSealedOrFinal ? 'final' : ''} class $concreteName${data.genericsDefinitionTemplate} $_concreteSuper { $_concreteConstructor $_concreteFromJsonConstructor @@ -86,7 +86,7 @@ $_toJson } -abstract class ${constructor.redirectedName}${data.genericsDefinitionTemplate} $_superKeyword ${data.name}${data.genericsParameterTemplate}$interfaces { +${data.shouldMarkSealedOrFinal ? 'sealed' : 'abstract'} class ${constructor.redirectedName}${data.genericsDefinitionTemplate} $_superKeyword ${data.name}${data.genericsParameterTemplate}$interfaces { $_isConst factory ${constructor.redirectedName}(${constructor.parameters.asExpandedDefinition}) = $concreteName${data.genericsParameterTemplate}; $_privateConcreteConstructor diff --git a/packages/freezed/test/finalized_test.dart b/packages/freezed/test/finalized_test.dart new file mode 100644 index 00000000..2e57da1a --- /dev/null +++ b/packages/freezed/test/finalized_test.dart @@ -0,0 +1,84 @@ +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:build_test/build_test.dart'; +import 'package:test/test.dart'; + +void main() { + group('single constructor', () { + test( + 'causes pattern_never_matches_value_type warning when trying to match on pattern that can never match', + () async { + final main = await resolveSources( + { + 'freezed|test/integration/main.dart': r''' +library main; +import 'finalized.dart'; + +void main() { + switch (const FinalizedFoo()) { + case FinalizedBar(): + break; + + case FinalizedFoo(): + break; + } +} +''', + }, + (r) => r.findLibraryByName('main'), + ); + + final errorResult = await main!.session + .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + + expect(errorResult.errors, hasLength(1)); + + final [error] = errorResult.errors; + + expect(error.errorCode.errorSeverity, ErrorSeverity.WARNING); + expect(error.errorCode.name, 'PATTERN_NEVER_MATCHES_VALUE_TYPE'); + }); + }); + + group('multiple constructors', () { + test( + 'causes pattern_never_matches_value_type warning when trying to match on pattern that can never match', + () async { + final main = await resolveSources( + { + 'freezed|test/integration/main.dart': r''' +library main; +import 'finalized.dart'; + +void main() { + switch (const FinalizedMultiple.b()) { + case FinalizedBar(): + break; + + case FinalizedMultipleA(): + break; + + case FinalizedMultipleB(): + break; + + case FinalizedMultipleC(): + break; + } +} +''', + }, + (r) => r.findLibraryByName('main'), + ); + + final errorResult = await main!.session + .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + + expect(errorResult.errors, hasLength(1)); + + final [error] = errorResult.errors; + + expect(error.errorCode.errorSeverity, ErrorSeverity.WARNING); + expect(error.errorCode.name, 'PATTERN_NEVER_MATCHES_VALUE_TYPE'); + }); + }); +} diff --git a/packages/freezed/test/integration/finalized.dart b/packages/freezed/test/integration/finalized.dart new file mode 100644 index 00000000..6aeacd32 --- /dev/null +++ b/packages/freezed/test/integration/finalized.dart @@ -0,0 +1,24 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'finalized.freezed.dart'; + +@Freezed(finalize: true) +sealed class FinalizedFoo with _$FinalizedFoo { + const FinalizedFoo._(); + const factory FinalizedFoo() = _FinalizedFoo; +} + +@Freezed(finalize: true) +sealed class FinalizedBar with _$FinalizedBar { + const FinalizedBar._(); + const factory FinalizedBar() = _FinalizedBar; +} + +@Freezed(finalize: true) +sealed class FinalizedMultiple with _$FinalizedMultiple { + const FinalizedMultiple._(); + + const factory FinalizedMultiple.a() = FinalizedMultipleA; + const factory FinalizedMultiple.b() = FinalizedMultipleB; + const factory FinalizedMultiple.c() = FinalizedMultipleC; +} diff --git a/packages/freezed/test/source_gen_src.dart b/packages/freezed/test/source_gen_src.dart index 0369afa1..4af7dc7a 100644 --- a/packages/freezed/test/source_gen_src.dart +++ b/packages/freezed/test/source_gen_src.dart @@ -224,3 +224,30 @@ abstract class AstractClass { class _AstractClass implements AstractClass { const _AstractClass(); } + +@ShouldThrow( + '@freezed classes configured with [finalize: true] must be sealed', +) +@Freezed(finalize: true) +class FinalizedClassWithoutSealedKeyword { + FinalizedClassWithoutSealedKeyword._(); + factory FinalizedClassWithoutSealedKeyword() = + _FinalizedClassWithoutSealedKeyword; +} + +class _FinalizedClassWithoutSealedKeyword + implements FinalizedClassWithoutSealedKeyword { + _FinalizedClassWithoutSealedKeyword(); +} + +@ShouldThrow( + '@freezed classes configured with [finalize: true] require a MyClass._() constructor', +) +@Freezed(finalize: true) +sealed class FinalizedClassWithoutPrivateConstructor { + factory FinalizedClassWithoutPrivateConstructor() = + _FinalizedClassWithoutPrivateConstructor; +} + +class _FinalizedClassWithoutPrivateConstructor + implements FinalizedClassWithoutPrivateConstructor {} diff --git a/packages/freezed_annotation/lib/freezed_annotation.dart b/packages/freezed_annotation/lib/freezed_annotation.dart index 28e25f6e..d50cca78 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.dart @@ -219,6 +219,7 @@ class Freezed { this.makeCollectionsUnmodifiable, this.addImplicitFinal = true, this.genericArgumentFactories = false, + this.finalize = false, }); /// Decode the options from a build.yaml @@ -476,6 +477,46 @@ class Freezed { /// ``` final bool genericArgumentFactories; + /// Whether to add `sealed`/`final` modifiers to the generated classes. + /// + /// Defaults to false. + /// + /// This makes the generated classes `sealed` and `final` by default, + /// so when using them in a switch statement, the analzyer will warn you + /// if you try to match agains a pattern that will never match the type. + /// + /// When configuring a class with `finalize: true`, it also needs to be + /// `sealed` and have a private no-args constructor. + /// + /// ```dart + /// @Freezed(finalize: true) + /// sealed class Foo with _$Foo { + /// const Foo._(); + /// const factory Foo() = _Foo; + /// } + /// + /// @Freezed(finalize: true) + /// sealed class Bar with _$Bar { + /// const Bar._(); + /// const factory Bar() = _Bar; + /// } + /// + /// void main() { + /// switch (Foo()) { + /// // The analyzer will yield a warning that this case can never match, + /// // because all subclasses of Foo are sealed/final, so it is guaranteed + /// // that instances of type Bar can never also be of type Foo. + /// case Bar(): + /// // ... + /// break; + /// + /// case Foo(): + /// // ... + /// break; + /// } + /// ``` + final bool finalize; + /// Options for customizing the generation of `map` functions /// /// If null, picks up the default values from the project's `build.yaml`. diff --git a/packages/freezed_annotation/lib/freezed_annotation.g.dart b/packages/freezed_annotation/lib/freezed_annotation.g.dart index 5f3a0dd3..fb89bd8c 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.g.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.g.dart @@ -37,6 +37,7 @@ Freezed _$FreezedFromJson(Map json) => Freezed( addImplicitFinal: json['add_implicit_final'] as bool? ?? true, genericArgumentFactories: json['generic_argument_factories'] as bool? ?? false, + finalize: json['finalize'] as bool? ?? false, ); const _$FreezedUnionCaseEnumMap = { From e36cd5f0f3e4b0bedd5a06edfc59a7a6b69e31bf Mon Sep 17 00:00:00 2001 From: Hauke Jaeger Date: Sat, 31 Aug 2024 16:35:58 +0200 Subject: [PATCH 2/7] fix: no longer require finalized classes to be sealed and have a no-args constructor --- .../freezed/lib/src/freezed_generator.dart | 24 ----------------- packages/freezed/test/source_gen_src.dart | 27 ------------------- 2 files changed, 51 deletions(-) diff --git a/packages/freezed/lib/src/freezed_generator.dart b/packages/freezed/lib/src/freezed_generator.dart index 55003ba0..cdff49d2 100644 --- a/packages/freezed/lib/src/freezed_generator.dart +++ b/packages/freezed/lib/src/freezed_generator.dart @@ -95,10 +95,6 @@ class FreezedGenerator extends ParserGenerator { _assertValidFieldUsage(field, shouldUseExtends: shouldUseExtends); } - if (configs.finalize) { - _assertValidFinalizedUsage(declaration); - } - final constructorsNeedsGeneration = await _parseConstructorsNeedsGeneration( buildStep, declaration, @@ -225,26 +221,6 @@ class FreezedGenerator extends ParserGenerator { } } - void _assertValidFinalizedUsage(ClassDeclaration declaration) { - if (declaration.sealedKeyword == null) { - throw InvalidGenerationSourceError( - '@freezed classes configured with [finalize: true] must be sealed', - element: declaration.declaredElement, - ); - } - - final hasPrivateConstructor = declaration.constructors.any((ctor) { - return ctor.name?.lexeme == '_'; - }); - - if (!hasPrivateConstructor) { - throw InvalidGenerationSourceError( - '@freezed classes configured with [finalize: true] require a MyClass._() constructor', - element: declaration.declaredElement, - ); - } - } - Future _needsJsonSerializable( BuildStep buildStep, GlobalData globalData, diff --git a/packages/freezed/test/source_gen_src.dart b/packages/freezed/test/source_gen_src.dart index 4af7dc7a..0369afa1 100644 --- a/packages/freezed/test/source_gen_src.dart +++ b/packages/freezed/test/source_gen_src.dart @@ -224,30 +224,3 @@ abstract class AstractClass { class _AstractClass implements AstractClass { const _AstractClass(); } - -@ShouldThrow( - '@freezed classes configured with [finalize: true] must be sealed', -) -@Freezed(finalize: true) -class FinalizedClassWithoutSealedKeyword { - FinalizedClassWithoutSealedKeyword._(); - factory FinalizedClassWithoutSealedKeyword() = - _FinalizedClassWithoutSealedKeyword; -} - -class _FinalizedClassWithoutSealedKeyword - implements FinalizedClassWithoutSealedKeyword { - _FinalizedClassWithoutSealedKeyword(); -} - -@ShouldThrow( - '@freezed classes configured with [finalize: true] require a MyClass._() constructor', -) -@Freezed(finalize: true) -sealed class FinalizedClassWithoutPrivateConstructor { - factory FinalizedClassWithoutPrivateConstructor() = - _FinalizedClassWithoutPrivateConstructor; -} - -class _FinalizedClassWithoutPrivateConstructor - implements FinalizedClassWithoutPrivateConstructor {} From a81420f7f28d9caf0be672ac5ecf3d32df8d4323 Mon Sep 17 00:00:00 2001 From: Hauke Jaeger Date: Sat, 31 Aug 2024 16:36:32 +0200 Subject: [PATCH 3/7] fix: read finalize config parameter from build.yaml --- packages/freezed/lib/src/freezed_generator.dart | 8 ++++++-- packages/freezed_annotation/lib/freezed_annotation.dart | 4 ++-- packages/freezed_annotation/lib/freezed_annotation.g.dart | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/freezed/lib/src/freezed_generator.dart b/packages/freezed/lib/src/freezed_generator.dart index cdff49d2..cceae998 100644 --- a/packages/freezed/lib/src/freezed_generator.dart +++ b/packages/freezed/lib/src/freezed_generator.dart @@ -146,7 +146,7 @@ class FreezedGenerator extends ParserGenerator { genericsParameterTemplate: GenericsParameterTemplate.fromGenericElement( declaration.declaredElement!.typeParameters, ), - shouldMarkSealedOrFinal: configs.finalize, + shouldMarkSealedOrFinal: configs.finalize ?? false, ); } @@ -599,7 +599,11 @@ class FreezedGenerator extends ParserGenerator { orElse: () => _buildYamlConfigs.fromJson, ), addImplicitFinal: annotation.getField('addImplicitFinal')!.toBoolValue()!, - finalize: annotation.getField('finalize')!.toBoolValue()!, + finalize: annotation.decodeField( + 'finalize', + decode: (obj) => obj.toBoolValue(), + orElse: () => _buildYamlConfigs.finalize, + ), map: annotation.decodeField( 'map', decode: (obj) { diff --git a/packages/freezed_annotation/lib/freezed_annotation.dart b/packages/freezed_annotation/lib/freezed_annotation.dart index d50cca78..7e35eb6a 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.dart @@ -219,7 +219,7 @@ class Freezed { this.makeCollectionsUnmodifiable, this.addImplicitFinal = true, this.genericArgumentFactories = false, - this.finalize = false, + this.finalize, }); /// Decode the options from a build.yaml @@ -515,7 +515,7 @@ class Freezed { /// break; /// } /// ``` - final bool finalize; + final bool? finalize; /// Options for customizing the generation of `map` functions /// diff --git a/packages/freezed_annotation/lib/freezed_annotation.g.dart b/packages/freezed_annotation/lib/freezed_annotation.g.dart index fb89bd8c..fc33d709 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.g.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.g.dart @@ -37,7 +37,7 @@ Freezed _$FreezedFromJson(Map json) => Freezed( addImplicitFinal: json['add_implicit_final'] as bool? ?? true, genericArgumentFactories: json['generic_argument_factories'] as bool? ?? false, - finalize: json['finalize'] as bool? ?? false, + finalize: json['finalize'] as bool?, ); const _$FreezedUnionCaseEnumMap = { From ec1e3f3c20f78a559923e905067cf222c09e3f8c Mon Sep 17 00:00:00 2001 From: Hauke Jaeger Date: Sat, 31 Aug 2024 16:34:49 +0200 Subject: [PATCH 4/7] refactor: simplify finalized_test.dart --- packages/freezed/test/finalized_test.dart | 4 ++-- packages/freezed/test/integration/finalized.dart | 14 +++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/freezed/test/finalized_test.dart b/packages/freezed/test/finalized_test.dart index 2e57da1a..fd4bc94f 100644 --- a/packages/freezed/test/finalized_test.dart +++ b/packages/freezed/test/finalized_test.dart @@ -15,7 +15,7 @@ library main; import 'finalized.dart'; void main() { - switch (const FinalizedFoo()) { + switch (FinalizedFoo()) { case FinalizedBar(): break; @@ -51,7 +51,7 @@ library main; import 'finalized.dart'; void main() { - switch (const FinalizedMultiple.b()) { + switch (FinalizedMultiple.b()) { case FinalizedBar(): break; diff --git a/packages/freezed/test/integration/finalized.dart b/packages/freezed/test/integration/finalized.dart index 6aeacd32..66172072 100644 --- a/packages/freezed/test/integration/finalized.dart +++ b/packages/freezed/test/integration/finalized.dart @@ -4,21 +4,17 @@ part 'finalized.freezed.dart'; @Freezed(finalize: true) sealed class FinalizedFoo with _$FinalizedFoo { - const FinalizedFoo._(); - const factory FinalizedFoo() = _FinalizedFoo; + factory FinalizedFoo() = _FinalizedFoo; } @Freezed(finalize: true) sealed class FinalizedBar with _$FinalizedBar { - const FinalizedBar._(); - const factory FinalizedBar() = _FinalizedBar; + factory FinalizedBar() = _FinalizedBar; } @Freezed(finalize: true) sealed class FinalizedMultiple with _$FinalizedMultiple { - const FinalizedMultiple._(); - - const factory FinalizedMultiple.a() = FinalizedMultipleA; - const factory FinalizedMultiple.b() = FinalizedMultipleB; - const factory FinalizedMultiple.c() = FinalizedMultipleC; + factory FinalizedMultiple.a() = FinalizedMultipleA; + factory FinalizedMultiple.b() = FinalizedMultipleB; + factory FinalizedMultiple.c() = FinalizedMultipleC; } From a337fd7996e9512a9c099c40bda51e6d21f3be49 Mon Sep 17 00:00:00 2001 From: Hauke Jaeger Date: Sun, 1 Sep 2024 17:49:08 +0200 Subject: [PATCH 5/7] feat: explicitly set finalize default value to false --- .../freezed/lib/src/freezed_generator.dart | 2 +- packages/freezed/test/finalized_test.dart | 61 +++++++++++++++++++ .../freezed/test/integration/finalized.dart | 12 ++++ .../lib/freezed_annotation.dart | 1 + .../lib/freezed_annotation.g.dart | 2 +- .../freezed_annotation/test/freezed_test.dart | 1 + 6 files changed, 77 insertions(+), 2 deletions(-) diff --git a/packages/freezed/lib/src/freezed_generator.dart b/packages/freezed/lib/src/freezed_generator.dart index cceae998..6292b8b5 100644 --- a/packages/freezed/lib/src/freezed_generator.dart +++ b/packages/freezed/lib/src/freezed_generator.dart @@ -146,7 +146,7 @@ class FreezedGenerator extends ParserGenerator { genericsParameterTemplate: GenericsParameterTemplate.fromGenericElement( declaration.declaredElement!.typeParameters, ), - shouldMarkSealedOrFinal: configs.finalize ?? false, + shouldMarkSealedOrFinal: configs.finalize!, ); } diff --git a/packages/freezed/test/finalized_test.dart b/packages/freezed/test/finalized_test.dart index fd4bc94f..4b5e507d 100644 --- a/packages/freezed/test/finalized_test.dart +++ b/packages/freezed/test/finalized_test.dart @@ -81,4 +81,65 @@ void main() { expect(error.errorCode.name, 'PATTERN_NEVER_MATCHES_VALUE_TYPE'); }); }); + + group('disabled (finalize: false)', () { + test('finalize is disabled by default', () async { + final main = await resolveSources( + { + 'freezed|test/integration/main.dart': r''' +library main; +import 'finalized.dart'; + +void main() { + switch (FinalizedDisabledFoo()) { + case FinalizedDisabledBar(): + break; + + case FinalizedDisabledFoo(): + break; + } +} +''', + }, + (r) => r.findLibraryByName('main'), + ); + + final errorResult = await main!.session + .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + + // the absence of a warning means that the generated subclasses of FinalizedDisabled + // or not sealed/final, and therefore it is disabled by default + expect(errorResult.errors, isEmpty); + }); + }); + group('default config', () { + test('finalize is disabled by default', () async { + final main = await resolveSources( + { + 'freezed|test/integration/main.dart': r''' +library main; +import 'finalized.dart'; + +void main() { + switch (FinalizedDefaultFoo()) { + case FinalizedDefaultBar(): + break; + + case FinalizedDefaultFoo(): + break; + } +} +''', + }, + (r) => r.findLibraryByName('main'), + ); + + final errorResult = await main!.session + .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + + // the absence of a warning means that the generated subclasses of FinalizedDefault + // or not sealed/final, and therefore it is disabled by default + expect(errorResult.errors, isEmpty); + }); + }); } diff --git a/packages/freezed/test/integration/finalized.dart b/packages/freezed/test/integration/finalized.dart index 66172072..487b5d94 100644 --- a/packages/freezed/test/integration/finalized.dart +++ b/packages/freezed/test/integration/finalized.dart @@ -18,3 +18,15 @@ sealed class FinalizedMultiple with _$FinalizedMultiple { factory FinalizedMultiple.b() = FinalizedMultipleB; factory FinalizedMultiple.c() = FinalizedMultipleC; } + +@Freezed() +sealed class FinalizedDefault with _$FinalizedDefault { + factory FinalizedDefault.foo() = FinalizedDefaultFoo; + factory FinalizedDefault.bar() = FinalizedDefaultBar; +} + +@Freezed(finalize: false) +sealed class FinalizedDisabled with _$FinalizedDisabled { + factory FinalizedDisabled.foo() = FinalizedDisabledFoo; + factory FinalizedDisabled.bar() = FinalizedDisabledBar; +} diff --git a/packages/freezed_annotation/lib/freezed_annotation.dart b/packages/freezed_annotation/lib/freezed_annotation.dart index 7e35eb6a..239ccec3 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.dart @@ -515,6 +515,7 @@ class Freezed { /// break; /// } /// ``` + @JsonKey(defaultValue: false) final bool? finalize; /// Options for customizing the generation of `map` functions diff --git a/packages/freezed_annotation/lib/freezed_annotation.g.dart b/packages/freezed_annotation/lib/freezed_annotation.g.dart index fc33d709..fb89bd8c 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.g.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.g.dart @@ -37,7 +37,7 @@ Freezed _$FreezedFromJson(Map json) => Freezed( addImplicitFinal: json['add_implicit_final'] as bool? ?? true, genericArgumentFactories: json['generic_argument_factories'] as bool? ?? false, - finalize: json['finalize'] as bool?, + finalize: json['finalize'] as bool? ?? false, ); const _$FreezedUnionCaseEnumMap = { diff --git a/packages/freezed_annotation/test/freezed_test.dart b/packages/freezed_annotation/test/freezed_test.dart index 3a8b205c..d04b34e4 100644 --- a/packages/freezed_annotation/test/freezed_test.dart +++ b/packages/freezed_annotation/test/freezed_test.dart @@ -151,6 +151,7 @@ void main() { expect(defaultValue.unionValueCase, isNull); expect(defaultValue.when, isNull); expect(defaultValue.makeCollectionsUnmodifiable, isTrue); + expect(defaultValue.finalize, isFalse); }); test('.fromJson({map: x})', () { From 4774262e93bfe068feb9bb79326891f33b57b925 Mon Sep 17 00:00:00 2001 From: Hauke Jaeger Date: Sun, 1 Sep 2024 16:30:26 +0200 Subject: [PATCH 6/7] fix: add dependency override frontend_server_client: ^4.0.0 Fixes an issue where build_runner was unable to generate sources after installing dependencies via `flutter pub downgrade` in GitHub actions. --- packages/freezed/pubspec_overrides.yaml | 8 ++++++++ packages/freezed_annotation/pubspec_overrides.yaml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/freezed/pubspec_overrides.yaml b/packages/freezed/pubspec_overrides.yaml index 79413790..46d09513 100644 --- a/packages/freezed/pubspec_overrides.yaml +++ b/packages/freezed/pubspec_overrides.yaml @@ -13,3 +13,11 @@ dependency_overrides: quiver: ^3.2.0 # watcher 1.0.0 extends, implements, or mixens sealed class 'FileSystemEvent' watcher: ^1.1.0 + # There seems to be a regression with Flutter ^3.24.0 + # that causes an error when generating sources using the build_runner + # after running `flutter pub downgrade` in GitHub actions, + # because it then installs frontend_server_client: ^3.0.0: + # Could not find a command named + # "/path/to/flutter/stable-3.24.0-x64/bin/cache/dart-sdk/bin/snapshots/frontend_server.dart.snapshot". + # see https://github.com/dart-lang/build/issues/3733#issuecomment-2272082820 + frontend_server_client: ^4.0.0 diff --git a/packages/freezed_annotation/pubspec_overrides.yaml b/packages/freezed_annotation/pubspec_overrides.yaml index 86d9c214..d486e96e 100644 --- a/packages/freezed_annotation/pubspec_overrides.yaml +++ b/packages/freezed_annotation/pubspec_overrides.yaml @@ -11,3 +11,11 @@ dependency_overrides: pub_semver: ^2.1.3 # watcher 1.0.0 extends, implements, or mixens sealed class 'FileSystemEvent' watcher: ^1.1.0 + # There seems to be a regression with Flutter ^3.24.0 + # that causes an error when generating sources using the build_runner + # after running `flutter pub downgrade` in GitHub actions, + # because it then installs frontend_server_client: ^3.0.0: + # Could not find a command named + # "/path/to/flutter/stable-3.24.0-x64/bin/cache/dart-sdk/bin/snapshots/frontend_server.dart.snapshot". + # see https://github.com/dart-lang/build/issues/3733#issuecomment-2272082820 + frontend_server_client: ^4.0.0 From 2ef0d8bc83898ccbd65311e164d3fe3c89d2461e Mon Sep 17 00:00:00 2001 From: Hauke Jaeger Date: Mon, 2 Sep 2024 09:06:04 +0200 Subject: [PATCH 7/7] feat: always mark generated classes as final/sealed Removes [finalize: bool] configuration parameter and instead makes it the default. --- packages/_internal/lib/models.dart | 1 - packages/_internal/lib/models.freezed.dart | 37 +---- .../freezed/lib/src/freezed_generator.dart | 6 - packages/freezed/lib/src/models.dart | 1 - packages/freezed/lib/src/models.freezed.dart | 37 +---- .../lib/src/templates/concrete_template.dart | 4 +- packages/freezed/test/finalized_test.dart | 129 +++++------------- .../freezed/test/integration/finalized.dart | 18 +-- .../lib/freezed_annotation.dart | 42 ------ .../lib/freezed_annotation.g.dart | 1 - .../freezed_annotation/test/freezed_test.dart | 1 - 11 files changed, 54 insertions(+), 223 deletions(-) diff --git a/packages/_internal/lib/models.dart b/packages/_internal/lib/models.dart index 9a980fa0..7046bdf5 100644 --- a/packages/_internal/lib/models.dart +++ b/packages/_internal/lib/models.dart @@ -102,7 +102,6 @@ class Data with _$Data { required GenericsParameterTemplate genericsParameterTemplate, required bool shouldUseExtends, required bool genericArgumentFactories, - required bool shouldMarkSealedOrFinal, }) = _Data; } diff --git a/packages/_internal/lib/models.freezed.dart b/packages/_internal/lib/models.freezed.dart index 29fecde3..ac1ce9f4 100644 --- a/packages/_internal/lib/models.freezed.dart +++ b/packages/_internal/lib/models.freezed.dart @@ -1098,7 +1098,6 @@ mixin _$Data { throw _privateConstructorUsedError; bool get shouldUseExtends => throw _privateConstructorUsedError; bool get genericArgumentFactories => throw _privateConstructorUsedError; - bool get shouldMarkSealedOrFinal => throw _privateConstructorUsedError; /// Create a copy of Data /// with the given fields replaced by the non-null parameter values. @@ -1127,8 +1126,7 @@ abstract class $DataCopyWith<$Res> { GenericsDefinitionTemplate genericsDefinitionTemplate, GenericsParameterTemplate genericsParameterTemplate, bool shouldUseExtends, - bool genericArgumentFactories, - bool shouldMarkSealedOrFinal}); + bool genericArgumentFactories}); $MapConfigCopyWith<$Res> get map; $WhenConfigCopyWith<$Res> get when; @@ -1165,7 +1163,6 @@ class _$DataCopyWithImpl<$Res, $Val extends Data> Object? genericsParameterTemplate = null, Object? shouldUseExtends = null, Object? genericArgumentFactories = null, - Object? shouldMarkSealedOrFinal = null, }) { return _then(_value.copyWith( name: null == name @@ -1232,10 +1229,6 @@ class _$DataCopyWithImpl<$Res, $Val extends Data> ? _value.genericArgumentFactories : genericArgumentFactories // ignore: cast_nullable_to_non_nullable as bool, - shouldMarkSealedOrFinal: null == shouldMarkSealedOrFinal - ? _value.shouldMarkSealedOrFinal - : shouldMarkSealedOrFinal // ignore: cast_nullable_to_non_nullable - as bool, ) as $Val); } @@ -1283,8 +1276,7 @@ abstract class _$$DataImplCopyWith<$Res> implements $DataCopyWith<$Res> { GenericsDefinitionTemplate genericsDefinitionTemplate, GenericsParameterTemplate genericsParameterTemplate, bool shouldUseExtends, - bool genericArgumentFactories, - bool shouldMarkSealedOrFinal}); + bool genericArgumentFactories}); @override $MapConfigCopyWith<$Res> get map; @@ -1320,7 +1312,6 @@ class __$$DataImplCopyWithImpl<$Res> Object? genericsParameterTemplate = null, Object? shouldUseExtends = null, Object? genericArgumentFactories = null, - Object? shouldMarkSealedOrFinal = null, }) { return _then(_$DataImpl( name: null == name @@ -1387,10 +1378,6 @@ class __$$DataImplCopyWithImpl<$Res> ? _value.genericArgumentFactories : genericArgumentFactories // ignore: cast_nullable_to_non_nullable as bool, - shouldMarkSealedOrFinal: null == shouldMarkSealedOrFinal - ? _value.shouldMarkSealedOrFinal - : shouldMarkSealedOrFinal // ignore: cast_nullable_to_non_nullable - as bool, )); } } @@ -1414,8 +1401,7 @@ class _$DataImpl implements _Data { required this.genericsDefinitionTemplate, required this.genericsParameterTemplate, required this.shouldUseExtends, - required this.genericArgumentFactories, - required this.shouldMarkSealedOrFinal}) + required this.genericArgumentFactories}) : assert(constructors.isNotEmpty), _concretePropertiesName = concretePropertiesName, _constructors = constructors; @@ -1465,12 +1451,10 @@ class _$DataImpl implements _Data { final bool shouldUseExtends; @override final bool genericArgumentFactories; - @override - final bool shouldMarkSealedOrFinal; @override String toString() { - return 'Data(name: $name, unionKey: $unionKey, generateCopyWith: $generateCopyWith, generateEqual: $generateEqual, generateToString: $generateToString, map: $map, when: $when, generateFromJson: $generateFromJson, generateToJson: $generateToJson, makeCollectionsImmutable: $makeCollectionsImmutable, concretePropertiesName: $concretePropertiesName, constructors: $constructors, genericsDefinitionTemplate: $genericsDefinitionTemplate, genericsParameterTemplate: $genericsParameterTemplate, shouldUseExtends: $shouldUseExtends, genericArgumentFactories: $genericArgumentFactories, shouldMarkSealedOrFinal: $shouldMarkSealedOrFinal)'; + return 'Data(name: $name, unionKey: $unionKey, generateCopyWith: $generateCopyWith, generateEqual: $generateEqual, generateToString: $generateToString, map: $map, when: $when, generateFromJson: $generateFromJson, generateToJson: $generateToJson, makeCollectionsImmutable: $makeCollectionsImmutable, concretePropertiesName: $concretePropertiesName, constructors: $constructors, genericsDefinitionTemplate: $genericsDefinitionTemplate, genericsParameterTemplate: $genericsParameterTemplate, shouldUseExtends: $shouldUseExtends, genericArgumentFactories: $genericArgumentFactories)'; } @override @@ -1511,10 +1495,7 @@ class _$DataImpl implements _Data { other.shouldUseExtends == shouldUseExtends) && (identical( other.genericArgumentFactories, genericArgumentFactories) || - other.genericArgumentFactories == genericArgumentFactories) && - (identical( - other.shouldMarkSealedOrFinal, shouldMarkSealedOrFinal) || - other.shouldMarkSealedOrFinal == shouldMarkSealedOrFinal)); + other.genericArgumentFactories == genericArgumentFactories)); } @override @@ -1535,8 +1516,7 @@ class _$DataImpl implements _Data { genericsDefinitionTemplate, genericsParameterTemplate, shouldUseExtends, - genericArgumentFactories, - shouldMarkSealedOrFinal); + genericArgumentFactories); /// Create a copy of Data /// with the given fields replaced by the non-null parameter values. @@ -1564,8 +1544,7 @@ abstract class _Data implements Data { required final GenericsDefinitionTemplate genericsDefinitionTemplate, required final GenericsParameterTemplate genericsParameterTemplate, required final bool shouldUseExtends, - required final bool genericArgumentFactories, - required final bool shouldMarkSealedOrFinal}) = _$DataImpl; + required final bool genericArgumentFactories}) = _$DataImpl; @override String get name; @@ -1599,8 +1578,6 @@ abstract class _Data implements Data { bool get shouldUseExtends; @override bool get genericArgumentFactories; - @override - bool get shouldMarkSealedOrFinal; /// Create a copy of Data /// with the given fields replaced by the non-null parameter values. diff --git a/packages/freezed/lib/src/freezed_generator.dart b/packages/freezed/lib/src/freezed_generator.dart index 6292b8b5..b0e044b3 100644 --- a/packages/freezed/lib/src/freezed_generator.dart +++ b/packages/freezed/lib/src/freezed_generator.dart @@ -146,7 +146,6 @@ class FreezedGenerator extends ParserGenerator { genericsParameterTemplate: GenericsParameterTemplate.fromGenericElement( declaration.declaredElement!.typeParameters, ), - shouldMarkSealedOrFinal: configs.finalize!, ); } @@ -599,11 +598,6 @@ class FreezedGenerator extends ParserGenerator { orElse: () => _buildYamlConfigs.fromJson, ), addImplicitFinal: annotation.getField('addImplicitFinal')!.toBoolValue()!, - finalize: annotation.decodeField( - 'finalize', - decode: (obj) => obj.toBoolValue(), - orElse: () => _buildYamlConfigs.finalize, - ), map: annotation.decodeField( 'map', decode: (obj) { diff --git a/packages/freezed/lib/src/models.dart b/packages/freezed/lib/src/models.dart index 38960a35..26a8ea58 100644 --- a/packages/freezed/lib/src/models.dart +++ b/packages/freezed/lib/src/models.dart @@ -92,7 +92,6 @@ class Data with _$Data { required GenericsParameterTemplate genericsParameterTemplate, required bool shouldUseExtends, required bool genericArgumentFactories, - required bool shouldMarkSealedOrFinal, }) = _Data; } diff --git a/packages/freezed/lib/src/models.freezed.dart b/packages/freezed/lib/src/models.freezed.dart index e82a62aa..b1c315f6 100644 --- a/packages/freezed/lib/src/models.freezed.dart +++ b/packages/freezed/lib/src/models.freezed.dart @@ -1054,7 +1054,6 @@ mixin _$Data { throw _privateConstructorUsedError; bool get shouldUseExtends => throw _privateConstructorUsedError; bool get genericArgumentFactories => throw _privateConstructorUsedError; - bool get shouldMarkSealedOrFinal => throw _privateConstructorUsedError; @JsonKey(includeFromJson: false, includeToJson: false) $DataCopyWith get copyWith => throw _privateConstructorUsedError; @@ -1081,8 +1080,7 @@ abstract class $DataCopyWith<$Res> { GenericsDefinitionTemplate genericsDefinitionTemplate, GenericsParameterTemplate genericsParameterTemplate, bool shouldUseExtends, - bool genericArgumentFactories, - bool shouldMarkSealedOrFinal}); + bool genericArgumentFactories}); $MapConfigCopyWith<$Res> get map; $WhenConfigCopyWith<$Res> get when; @@ -1117,7 +1115,6 @@ class _$DataCopyWithImpl<$Res, $Val extends Data> Object? genericsParameterTemplate = null, Object? shouldUseExtends = null, Object? genericArgumentFactories = null, - Object? shouldMarkSealedOrFinal = null, }) { return _then(_value.copyWith( name: null == name @@ -1184,10 +1181,6 @@ class _$DataCopyWithImpl<$Res, $Val extends Data> ? _value.genericArgumentFactories : genericArgumentFactories // ignore: cast_nullable_to_non_nullable as bool, - shouldMarkSealedOrFinal: null == shouldMarkSealedOrFinal - ? _value.shouldMarkSealedOrFinal - : shouldMarkSealedOrFinal // ignore: cast_nullable_to_non_nullable - as bool, ) as $Val); } @@ -1231,8 +1224,7 @@ abstract class _$$DataImplCopyWith<$Res> implements $DataCopyWith<$Res> { GenericsDefinitionTemplate genericsDefinitionTemplate, GenericsParameterTemplate genericsParameterTemplate, bool shouldUseExtends, - bool genericArgumentFactories, - bool shouldMarkSealedOrFinal}); + bool genericArgumentFactories}); @override $MapConfigCopyWith<$Res> get map; @@ -1266,7 +1258,6 @@ class __$$DataImplCopyWithImpl<$Res> Object? genericsParameterTemplate = null, Object? shouldUseExtends = null, Object? genericArgumentFactories = null, - Object? shouldMarkSealedOrFinal = null, }) { return _then(_$DataImpl( name: null == name @@ -1333,10 +1324,6 @@ class __$$DataImplCopyWithImpl<$Res> ? _value.genericArgumentFactories : genericArgumentFactories // ignore: cast_nullable_to_non_nullable as bool, - shouldMarkSealedOrFinal: null == shouldMarkSealedOrFinal - ? _value.shouldMarkSealedOrFinal - : shouldMarkSealedOrFinal // ignore: cast_nullable_to_non_nullable - as bool, )); } } @@ -1360,8 +1347,7 @@ class _$DataImpl implements _Data { required this.genericsDefinitionTemplate, required this.genericsParameterTemplate, required this.shouldUseExtends, - required this.genericArgumentFactories, - required this.shouldMarkSealedOrFinal}) + required this.genericArgumentFactories}) : assert(constructors.isNotEmpty), _concretePropertiesName = concretePropertiesName, _constructors = constructors; @@ -1411,12 +1397,10 @@ class _$DataImpl implements _Data { final bool shouldUseExtends; @override final bool genericArgumentFactories; - @override - final bool shouldMarkSealedOrFinal; @override String toString() { - return 'Data(name: $name, unionKey: $unionKey, generateCopyWith: $generateCopyWith, generateEqual: $generateEqual, generateToString: $generateToString, map: $map, when: $when, generateFromJson: $generateFromJson, generateToJson: $generateToJson, makeCollectionsImmutable: $makeCollectionsImmutable, concretePropertiesName: $concretePropertiesName, constructors: $constructors, genericsDefinitionTemplate: $genericsDefinitionTemplate, genericsParameterTemplate: $genericsParameterTemplate, shouldUseExtends: $shouldUseExtends, genericArgumentFactories: $genericArgumentFactories, shouldMarkSealedOrFinal: $shouldMarkSealedOrFinal)'; + return 'Data(name: $name, unionKey: $unionKey, generateCopyWith: $generateCopyWith, generateEqual: $generateEqual, generateToString: $generateToString, map: $map, when: $when, generateFromJson: $generateFromJson, generateToJson: $generateToJson, makeCollectionsImmutable: $makeCollectionsImmutable, concretePropertiesName: $concretePropertiesName, constructors: $constructors, genericsDefinitionTemplate: $genericsDefinitionTemplate, genericsParameterTemplate: $genericsParameterTemplate, shouldUseExtends: $shouldUseExtends, genericArgumentFactories: $genericArgumentFactories)'; } @override @@ -1457,10 +1441,7 @@ class _$DataImpl implements _Data { other.shouldUseExtends == shouldUseExtends) && (identical( other.genericArgumentFactories, genericArgumentFactories) || - other.genericArgumentFactories == genericArgumentFactories) && - (identical( - other.shouldMarkSealedOrFinal, shouldMarkSealedOrFinal) || - other.shouldMarkSealedOrFinal == shouldMarkSealedOrFinal)); + other.genericArgumentFactories == genericArgumentFactories)); } @override @@ -1481,8 +1462,7 @@ class _$DataImpl implements _Data { genericsDefinitionTemplate, genericsParameterTemplate, shouldUseExtends, - genericArgumentFactories, - shouldMarkSealedOrFinal); + genericArgumentFactories); @JsonKey(includeFromJson: false, includeToJson: false) @override @@ -1508,8 +1488,7 @@ abstract class _Data implements Data { required final GenericsDefinitionTemplate genericsDefinitionTemplate, required final GenericsParameterTemplate genericsParameterTemplate, required final bool shouldUseExtends, - required final bool genericArgumentFactories, - required final bool shouldMarkSealedOrFinal}) = _$DataImpl; + required final bool genericArgumentFactories}) = _$DataImpl; @override String get name; @@ -1544,8 +1523,6 @@ abstract class _Data implements Data { @override bool get genericArgumentFactories; @override - bool get shouldMarkSealedOrFinal; - @override @JsonKey(includeFromJson: false, includeToJson: false) _$$DataImplCopyWith<_$DataImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/packages/freezed/lib/src/templates/concrete_template.dart b/packages/freezed/lib/src/templates/concrete_template.dart index 9187a652..f96b170b 100644 --- a/packages/freezed/lib/src/templates/concrete_template.dart +++ b/packages/freezed/lib/src/templates/concrete_template.dart @@ -64,7 +64,7 @@ ${copyWith?.concreteImpl(constructor.parameters) ?? ''} /// @nodoc $jsonSerializable ${constructor.decorators.join('\n')} -${data.shouldMarkSealedOrFinal ? 'final' : ''} class $concreteName${data.genericsDefinitionTemplate} $_concreteSuper { +final class $concreteName${data.genericsDefinitionTemplate} $_concreteSuper { $_concreteConstructor $_concreteFromJsonConstructor @@ -86,7 +86,7 @@ $_toJson } -${data.shouldMarkSealedOrFinal ? 'sealed' : 'abstract'} class ${constructor.redirectedName}${data.genericsDefinitionTemplate} $_superKeyword ${data.name}${data.genericsParameterTemplate}$interfaces { +sealed class ${constructor.redirectedName}${data.genericsDefinitionTemplate} $_superKeyword ${data.name}${data.genericsParameterTemplate}$interfaces { $_isConst factory ${constructor.redirectedName}(${constructor.parameters.asExpandedDefinition}) = $concreteName${data.genericsParameterTemplate}; $_privateConcreteConstructor diff --git a/packages/freezed/test/finalized_test.dart b/packages/freezed/test/finalized_test.dart index 4b5e507d..4846339e 100644 --- a/packages/freezed/test/finalized_test.dart +++ b/packages/freezed/test/finalized_test.dart @@ -4,13 +4,14 @@ import 'package:build_test/build_test.dart'; import 'package:test/test.dart'; void main() { - group('single constructor', () { - test( - 'causes pattern_never_matches_value_type warning when trying to match on pattern that can never match', - () async { - final main = await resolveSources( - { - 'freezed|test/integration/main.dart': r''' + group('marks generated classes as final and sealed', () { + group('single constructor', () { + test( + 'causes pattern_never_matches_value_type warning when trying to match on pattern that can never match', + () async { + final main = await resolveSources( + { + 'freezed|test/integration/main.dart': r''' library main; import 'finalized.dart'; @@ -24,29 +25,29 @@ void main() { } } ''', - }, - (r) => r.findLibraryByName('main'), - ); + }, + (r) => r.findLibraryByName('main'), + ); - final errorResult = await main!.session - .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + final errorResult = await main!.session + .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; - expect(errorResult.errors, hasLength(1)); + expect(errorResult.errors, hasLength(1)); - final [error] = errorResult.errors; + final [error] = errorResult.errors; - expect(error.errorCode.errorSeverity, ErrorSeverity.WARNING); - expect(error.errorCode.name, 'PATTERN_NEVER_MATCHES_VALUE_TYPE'); + expect(error.errorCode.errorSeverity, ErrorSeverity.WARNING); + expect(error.errorCode.name, 'PATTERN_NEVER_MATCHES_VALUE_TYPE'); + }); }); - }); - group('multiple constructors', () { - test( - 'causes pattern_never_matches_value_type warning when trying to match on pattern that can never match', - () async { - final main = await resolveSources( - { - 'freezed|test/integration/main.dart': r''' + group('multiple constructors', () { + test( + 'causes pattern_never_matches_value_type warning when trying to match on pattern that can never match', + () async { + final main = await resolveSources( + { + 'freezed|test/integration/main.dart': r''' library main; import 'finalized.dart'; @@ -66,80 +67,20 @@ void main() { } } ''', - }, - (r) => r.findLibraryByName('main'), - ); - - final errorResult = await main!.session - .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; - - expect(errorResult.errors, hasLength(1)); - - final [error] = errorResult.errors; - - expect(error.errorCode.errorSeverity, ErrorSeverity.WARNING); - expect(error.errorCode.name, 'PATTERN_NEVER_MATCHES_VALUE_TYPE'); - }); - }); - - group('disabled (finalize: false)', () { - test('finalize is disabled by default', () async { - final main = await resolveSources( - { - 'freezed|test/integration/main.dart': r''' -library main; -import 'finalized.dart'; - -void main() { - switch (FinalizedDisabledFoo()) { - case FinalizedDisabledBar(): - break; - - case FinalizedDisabledFoo(): - break; - } -} -''', - }, - (r) => r.findLibraryByName('main'), - ); - - final errorResult = await main!.session - .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + }, + (r) => r.findLibraryByName('main'), + ); - // the absence of a warning means that the generated subclasses of FinalizedDisabled - // or not sealed/final, and therefore it is disabled by default - expect(errorResult.errors, isEmpty); - }); - }); - group('default config', () { - test('finalize is disabled by default', () async { - final main = await resolveSources( - { - 'freezed|test/integration/main.dart': r''' -library main; -import 'finalized.dart'; + final errorResult = await main!.session + .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; -void main() { - switch (FinalizedDefaultFoo()) { - case FinalizedDefaultBar(): - break; - - case FinalizedDefaultFoo(): - break; - } -} -''', - }, - (r) => r.findLibraryByName('main'), - ); + expect(errorResult.errors, hasLength(1)); - final errorResult = await main!.session - .getErrors('/freezed/test/integration/main.dart') as ErrorsResult; + final [error] = errorResult.errors; - // the absence of a warning means that the generated subclasses of FinalizedDefault - // or not sealed/final, and therefore it is disabled by default - expect(errorResult.errors, isEmpty); + expect(error.errorCode.errorSeverity, ErrorSeverity.WARNING); + expect(error.errorCode.name, 'PATTERN_NEVER_MATCHES_VALUE_TYPE'); + }); }); }); } diff --git a/packages/freezed/test/integration/finalized.dart b/packages/freezed/test/integration/finalized.dart index 487b5d94..83c4ec49 100644 --- a/packages/freezed/test/integration/finalized.dart +++ b/packages/freezed/test/integration/finalized.dart @@ -2,31 +2,19 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'finalized.freezed.dart'; -@Freezed(finalize: true) +@freezed sealed class FinalizedFoo with _$FinalizedFoo { factory FinalizedFoo() = _FinalizedFoo; } -@Freezed(finalize: true) +@freezed sealed class FinalizedBar with _$FinalizedBar { factory FinalizedBar() = _FinalizedBar; } -@Freezed(finalize: true) +@freezed sealed class FinalizedMultiple with _$FinalizedMultiple { factory FinalizedMultiple.a() = FinalizedMultipleA; factory FinalizedMultiple.b() = FinalizedMultipleB; factory FinalizedMultiple.c() = FinalizedMultipleC; } - -@Freezed() -sealed class FinalizedDefault with _$FinalizedDefault { - factory FinalizedDefault.foo() = FinalizedDefaultFoo; - factory FinalizedDefault.bar() = FinalizedDefaultBar; -} - -@Freezed(finalize: false) -sealed class FinalizedDisabled with _$FinalizedDisabled { - factory FinalizedDisabled.foo() = FinalizedDisabledFoo; - factory FinalizedDisabled.bar() = FinalizedDisabledBar; -} diff --git a/packages/freezed_annotation/lib/freezed_annotation.dart b/packages/freezed_annotation/lib/freezed_annotation.dart index 239ccec3..28e25f6e 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.dart @@ -219,7 +219,6 @@ class Freezed { this.makeCollectionsUnmodifiable, this.addImplicitFinal = true, this.genericArgumentFactories = false, - this.finalize, }); /// Decode the options from a build.yaml @@ -477,47 +476,6 @@ class Freezed { /// ``` final bool genericArgumentFactories; - /// Whether to add `sealed`/`final` modifiers to the generated classes. - /// - /// Defaults to false. - /// - /// This makes the generated classes `sealed` and `final` by default, - /// so when using them in a switch statement, the analzyer will warn you - /// if you try to match agains a pattern that will never match the type. - /// - /// When configuring a class with `finalize: true`, it also needs to be - /// `sealed` and have a private no-args constructor. - /// - /// ```dart - /// @Freezed(finalize: true) - /// sealed class Foo with _$Foo { - /// const Foo._(); - /// const factory Foo() = _Foo; - /// } - /// - /// @Freezed(finalize: true) - /// sealed class Bar with _$Bar { - /// const Bar._(); - /// const factory Bar() = _Bar; - /// } - /// - /// void main() { - /// switch (Foo()) { - /// // The analyzer will yield a warning that this case can never match, - /// // because all subclasses of Foo are sealed/final, so it is guaranteed - /// // that instances of type Bar can never also be of type Foo. - /// case Bar(): - /// // ... - /// break; - /// - /// case Foo(): - /// // ... - /// break; - /// } - /// ``` - @JsonKey(defaultValue: false) - final bool? finalize; - /// Options for customizing the generation of `map` functions /// /// If null, picks up the default values from the project's `build.yaml`. diff --git a/packages/freezed_annotation/lib/freezed_annotation.g.dart b/packages/freezed_annotation/lib/freezed_annotation.g.dart index fb89bd8c..5f3a0dd3 100644 --- a/packages/freezed_annotation/lib/freezed_annotation.g.dart +++ b/packages/freezed_annotation/lib/freezed_annotation.g.dart @@ -37,7 +37,6 @@ Freezed _$FreezedFromJson(Map json) => Freezed( addImplicitFinal: json['add_implicit_final'] as bool? ?? true, genericArgumentFactories: json['generic_argument_factories'] as bool? ?? false, - finalize: json['finalize'] as bool? ?? false, ); const _$FreezedUnionCaseEnumMap = { diff --git a/packages/freezed_annotation/test/freezed_test.dart b/packages/freezed_annotation/test/freezed_test.dart index d04b34e4..3a8b205c 100644 --- a/packages/freezed_annotation/test/freezed_test.dart +++ b/packages/freezed_annotation/test/freezed_test.dart @@ -151,7 +151,6 @@ void main() { expect(defaultValue.unionValueCase, isNull); expect(defaultValue.when, isNull); expect(defaultValue.makeCollectionsUnmodifiable, isTrue); - expect(defaultValue.finalize, isFalse); }); test('.fromJson({map: x})', () {