Skip to content

Commit 0d0b28e

Browse files
author
Julien Zarka
committed
fix: include type parameters in switch cases and copyWith factories for sealed classes
- Add type parameters to switch case patterns for generic sealed classes - Include type parameters in copyWith factory implementations - Add comprehensive tests for both generic and non-generic sealed classes - Ensure backward compatibility with non-generic sealed classes Tests: - Verify generic sealed classes generate correct switch cases with type params - Verify copyWith factories include type parameters for generic sealed classes - Verify non-generic sealed classes continue to work correctly (backward compatibility)
1 parent 821021c commit 0d0b28e

File tree

5 files changed

+100
-3
lines changed

5 files changed

+100
-3
lines changed

packages/freezed/lib/src/templates/copy_with.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class CopyWith {
6464
leading = 'abstract mixin ';
6565
body =
6666
'''
67-
factory $_abstractClassName($clonedClassName$genericsParameter value, \$Res Function($clonedClassName$genericsParameter) _then) = $_implClassName;
67+
factory $_abstractClassName($clonedClassName$genericsParameter value, \$Res Function($clonedClassName$genericsParameter) _then) = $_implClassName${genericsParameter.append('\$Res')};
6868
${_copyWithPrototype('call')}
6969
7070
${_abstractDeepCopyMethods().join()}

packages/freezed/lib/src/templates/pattern_template.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ String _mapImpl(
216216
..writeln('final _that = this;')
217217
..writeln('switch (_that) {');
218218
for (final constructor in data.constructors) {
219-
buffer.write('case ${constructor.redirectedName}()');
219+
buffer.write('case ${constructor.redirectedName}${data.genericsParameterTemplate}()');
220220
if (!areCallbacksRequired) {
221221
buffer.write(' when ${constructor.callbackName} != null');
222222
}
@@ -288,7 +288,7 @@ String _whenImpl(
288288
..writeln('switch (_that) {');
289289

290290
for (final constructor in data.constructors) {
291-
buffer.write('case ${constructor.redirectedName}()');
291+
buffer.write('case ${constructor.redirectedName}${data.genericsParameterTemplate}()');
292292
if (!areCallbacksRequired) {
293293
buffer.write(' when ${constructor.callbackName} != null');
294294
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'package:freezed_annotation/freezed_annotation.dart';
2+
3+
part 'sealed_generic.freezed.dart';
4+
5+
@freezed
6+
sealed class SealedGeneric<T> with _$SealedGeneric<T> {
7+
const factory SealedGeneric.first(T data) = SealedGenericFirst<T>;
8+
const factory SealedGeneric.second(T data) = SealedGenericSecond<T>;
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'package:freezed_annotation/freezed_annotation.dart';
2+
3+
part 'sealed_non_generic.freezed.dart';
4+
5+
@freezed
6+
sealed class SealedNonGeneric with _$SealedNonGeneric {
7+
const factory SealedNonGeneric.first(String data) = SealedNonGenericFirst;
8+
const factory SealedNonGeneric.second(String data) = SealedNonGenericSecond;
9+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// ignore_for_file: prefer_const_constructors, omit_local_variable_types, deprecated_member_use_from_same_package
2+
import 'package:analyzer/dart/analysis/results.dart';
3+
import 'package:build_test/build_test.dart';
4+
import 'package:test/test.dart';
5+
6+
Future<void> main() async {
7+
test('sealed class with type parameters generates correct switch cases', () async {
8+
final library = await resolveSources(
9+
{'freezed|test/integration/sealed_generic_switch/sealed_generic.dart': useAssetReader},
10+
(r) => r.libraries.firstWhere(
11+
(element) =>
12+
element.firstFragment.source.uri.path.endsWith('sealed_generic_switch/sealed_generic.dart'),
13+
),
14+
);
15+
16+
const generatedPath =
17+
'/freezed/test/integration/sealed_generic_switch/sealed_generic.freezed.dart';
18+
final result = await library.session.getErrors(generatedPath);
19+
expect(result, isA<ErrorsResult>(), reason: 'Expected ErrorsResult for $generatedPath');
20+
final errorResult = result as ErrorsResult;
21+
22+
// The key test: verify that the generated code has no errors
23+
// This confirms that switch cases with type parameters are correctly generated
24+
expect(errorResult.errors, isEmpty);
25+
});
26+
27+
test('sealed class with type parameters - verify generated switch cases include type params', () async {
28+
// This test verifies the generated code content by checking that it compiles correctly
29+
// with the expected patterns. Since resolveSources generates files in memory and
30+
// the content isn't directly accessible, we verify correctness through compilation.
31+
final library = await resolveSources(
32+
{'freezed|test/integration/sealed_generic_switch/sealed_generic.dart': useAssetReader},
33+
(r) => r.libraries.firstWhere(
34+
(element) =>
35+
element.firstFragment.source.uri.path.endsWith('sealed_generic_switch/sealed_generic.dart'),
36+
),
37+
);
38+
39+
const generatedPath =
40+
'/freezed/test/integration/sealed_generic_switch/sealed_generic.freezed.dart';
41+
42+
// Verify the file exists and has no errors
43+
// This confirms that switch cases with type parameters are correctly generated
44+
// If the type parameters were missing, the analyzer would report errors
45+
final result = await library.session.getErrors(generatedPath);
46+
expect(result, isA<ErrorsResult>(), reason: 'Expected ErrorsResult for $generatedPath');
47+
final errorResult = result as ErrorsResult;
48+
expect(errorResult.errors, isEmpty,
49+
reason: 'Generated file should have no errors. '
50+
'This confirms that switch cases include type parameters (e.g., case SealedGenericFirst<T>():) '
51+
'and copyWith factories include type parameters (e.g., = _\$SealedGenericCopyWithImpl<T, \$Res>;), '
52+
'as missing type parameters would cause compilation errors.');
53+
});
54+
55+
test('sealed class without type parameters continues to work correctly', () async {
56+
// This test ensures backward compatibility - sealed classes without type parameters
57+
// should continue to work as before, without requiring type parameters in switch cases
58+
final library = await resolveSources(
59+
{'freezed|test/integration/sealed_generic_switch/sealed_non_generic.dart': useAssetReader},
60+
(r) => r.libraries.firstWhere(
61+
(element) =>
62+
element.firstFragment.source.uri.path.endsWith('sealed_generic_switch/sealed_non_generic.dart'),
63+
),
64+
);
65+
66+
const generatedPath =
67+
'/freezed/test/integration/sealed_generic_switch/sealed_non_generic.freezed.dart';
68+
final result = await library.session.getErrors(generatedPath);
69+
expect(result, isA<ErrorsResult>(), reason: 'Expected ErrorsResult for $generatedPath');
70+
final errorResult = result as ErrorsResult;
71+
72+
// Verify that the generated code has no errors
73+
// This confirms that sealed classes without type parameters continue to work correctly
74+
expect(errorResult.errors, isEmpty,
75+
reason: 'Sealed class without type parameters should generate error-free code. '
76+
'This ensures backward compatibility - non-generic sealed classes should work '
77+
'without requiring type parameters in switch cases.');
78+
});
79+
}

0 commit comments

Comments
 (0)