diff --git a/packages/freezed/lib/src/templates/copy_with.dart b/packages/freezed/lib/src/templates/copy_with.dart index 69cbecf3..d7e515e8 100644 --- a/packages/freezed/lib/src/templates/copy_with.dart +++ b/packages/freezed/lib/src/templates/copy_with.dart @@ -64,7 +64,7 @@ class CopyWith { leading = 'abstract mixin '; body = ''' - factory $_abstractClassName($clonedClassName$genericsParameter value, \$Res Function($clonedClassName$genericsParameter) _then) = $_implClassName; + factory $_abstractClassName($clonedClassName$genericsParameter value, \$Res Function($clonedClassName$genericsParameter) _then) = $_implClassName${genericsParameter.append('\$Res')}; ${_copyWithPrototype('call')} ${_abstractDeepCopyMethods().join()} diff --git a/packages/freezed/lib/src/templates/pattern_template.dart b/packages/freezed/lib/src/templates/pattern_template.dart index 0ad0a58d..8e0b7b54 100644 --- a/packages/freezed/lib/src/templates/pattern_template.dart +++ b/packages/freezed/lib/src/templates/pattern_template.dart @@ -216,7 +216,7 @@ String _mapImpl( ..writeln('final _that = this;') ..writeln('switch (_that) {'); for (final constructor in data.constructors) { - buffer.write('case ${constructor.redirectedName}()'); + buffer.write('case ${constructor.redirectedName}${data.genericsParameterTemplate}()'); if (!areCallbacksRequired) { buffer.write(' when ${constructor.callbackName} != null'); } @@ -288,7 +288,7 @@ String _whenImpl( ..writeln('switch (_that) {'); for (final constructor in data.constructors) { - buffer.write('case ${constructor.redirectedName}()'); + buffer.write('case ${constructor.redirectedName}${data.genericsParameterTemplate}()'); if (!areCallbacksRequired) { buffer.write(' when ${constructor.callbackName} != null'); } diff --git a/packages/freezed/test/integration/sealed_generic_switch/sealed_generic.dart b/packages/freezed/test/integration/sealed_generic_switch/sealed_generic.dart new file mode 100644 index 00000000..13b268b9 --- /dev/null +++ b/packages/freezed/test/integration/sealed_generic_switch/sealed_generic.dart @@ -0,0 +1,9 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'sealed_generic.freezed.dart'; + +@freezed +sealed class SealedGeneric with _$SealedGeneric { + const factory SealedGeneric.first(T data) = SealedGenericFirst; + const factory SealedGeneric.second(T data) = SealedGenericSecond; +} diff --git a/packages/freezed/test/integration/sealed_generic_switch/sealed_non_generic.dart b/packages/freezed/test/integration/sealed_generic_switch/sealed_non_generic.dart new file mode 100644 index 00000000..36de3926 --- /dev/null +++ b/packages/freezed/test/integration/sealed_generic_switch/sealed_non_generic.dart @@ -0,0 +1,9 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'sealed_non_generic.freezed.dart'; + +@freezed +sealed class SealedNonGeneric with _$SealedNonGeneric { + const factory SealedNonGeneric.first(String data) = SealedNonGenericFirst; + const factory SealedNonGeneric.second(String data) = SealedNonGenericSecond; +} diff --git a/packages/freezed/test/sealed_generic_switch_test.dart b/packages/freezed/test/sealed_generic_switch_test.dart new file mode 100644 index 00000000..47e6450b --- /dev/null +++ b/packages/freezed/test/sealed_generic_switch_test.dart @@ -0,0 +1,79 @@ +// ignore_for_file: prefer_const_constructors, omit_local_variable_types, deprecated_member_use_from_same_package +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:build_test/build_test.dart'; +import 'package:test/test.dart'; + +Future main() async { + test('sealed class with type parameters generates correct switch cases', () async { + final library = await resolveSources( + {'freezed|test/integration/sealed_generic_switch/sealed_generic.dart': useAssetReader}, + (r) => r.libraries.firstWhere( + (element) => + element.firstFragment.source.uri.path.endsWith('sealed_generic_switch/sealed_generic.dart'), + ), + ); + + const generatedPath = + '/freezed/test/integration/sealed_generic_switch/sealed_generic.freezed.dart'; + final result = await library.session.getErrors(generatedPath); + expect(result, isA(), reason: 'Expected ErrorsResult for $generatedPath'); + final errorResult = result as ErrorsResult; + + // The key test: verify that the generated code has no errors + // This confirms that switch cases with type parameters are correctly generated + expect(errorResult.errors, isEmpty); + }); + + test('sealed class with type parameters - verify generated switch cases include type params', () async { + // This test verifies the generated code content by checking that it compiles correctly + // with the expected patterns. Since resolveSources generates files in memory and + // the content isn't directly accessible, we verify correctness through compilation. + final library = await resolveSources( + {'freezed|test/integration/sealed_generic_switch/sealed_generic.dart': useAssetReader}, + (r) => r.libraries.firstWhere( + (element) => + element.firstFragment.source.uri.path.endsWith('sealed_generic_switch/sealed_generic.dart'), + ), + ); + + const generatedPath = + '/freezed/test/integration/sealed_generic_switch/sealed_generic.freezed.dart'; + + // Verify the file exists and has no errors + // This confirms that switch cases with type parameters are correctly generated + // If the type parameters were missing, the analyzer would report errors + final result = await library.session.getErrors(generatedPath); + expect(result, isA(), reason: 'Expected ErrorsResult for $generatedPath'); + final errorResult = result as ErrorsResult; + expect(errorResult.errors, isEmpty, + reason: 'Generated file should have no errors. ' + 'This confirms that switch cases include type parameters (e.g., case SealedGenericFirst():) ' + 'and copyWith factories include type parameters (e.g., = _\$SealedGenericCopyWithImpl;), ' + 'as missing type parameters would cause compilation errors.'); + }); + + test('sealed class without type parameters continues to work correctly', () async { + // This test ensures backward compatibility - sealed classes without type parameters + // should continue to work as before, without requiring type parameters in switch cases + final library = await resolveSources( + {'freezed|test/integration/sealed_generic_switch/sealed_non_generic.dart': useAssetReader}, + (r) => r.libraries.firstWhere( + (element) => + element.firstFragment.source.uri.path.endsWith('sealed_generic_switch/sealed_non_generic.dart'), + ), + ); + + const generatedPath = + '/freezed/test/integration/sealed_generic_switch/sealed_non_generic.freezed.dart'; + final result = await library.session.getErrors(generatedPath); + expect(result, isA(), reason: 'Expected ErrorsResult for $generatedPath'); + final errorResult = result as ErrorsResult; + + // Verify that the generated code has no errors + // This confirms that sealed classes without type parameters continue to work correctly + expect(errorResult.errors, isEmpty, + reason: 'Sealed class without type parameters should generate error-free code. ' + 'This ensures backward compatibility - non-generic sealed classes should work ' + 'without requiring type parameters in switch cases.'); + }); +}