Skip to content

Commit 7c28846

Browse files
author
Julien Zarka
committed
fix: include type parameters in switch cases and copyWith factories for sealed classes
Sealed classes with type parameters require explicit type parameters in pattern matching switch cases and copyWith factory method assignments. Without this, Dart's analyzer flags the generated code as non-exhaustive or incorrectly typed. Changes: - Add genericsParameterTemplate to switch case patterns in _mapImpl and _whenImpl - Add type parameters to copyWith factory method implementation class reference This fixes compilation errors for sealed classes with type parameters and eliminates the need for post-generation patching.
1 parent 821021c commit 7c28846

File tree

4 files changed

+72
-3
lines changed

4 files changed

+72
-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: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.toString().contains('sealed_generic'),
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+
final library = await resolveSources(
29+
{'freezed|test/integration/sealed_generic_switch/sealed_generic.dart': useAssetReader},
30+
(r) => r.libraries.firstWhere(
31+
(element) =>
32+
element.firstFragment.source.toString().contains('sealed_generic'),
33+
),
34+
);
35+
36+
const generatedPath =
37+
'/freezed/test/integration/sealed_generic_switch/sealed_generic.freezed.dart';
38+
39+
// Access the generated file through the session
40+
// The file should be available as it's a part of the library
41+
final fileResult = await library.session.getFile(generatedPath);
42+
expect(fileResult, isA<FileResult>(), reason: 'Expected FileResult for $generatedPath');
43+
final content = (fileResult as FileResult).content;
44+
45+
// Try to get content - if empty, the file might be generated in memory
46+
// In that case, we rely on the error check in the first test to verify correctness
47+
if (content.isNotEmpty) {
48+
// Verify that switch cases include type parameters
49+
expect(content, contains('case SealedGenericFirst<T>():'));
50+
expect(content, contains('case SealedGenericSecond<T>():'));
51+
52+
// Verify that copyWith factory includes type parameters
53+
expect(content, contains('= _\$SealedGenericCopyWithImpl<T, \$Res>;'));
54+
expect(content, contains('= _\$SealedGenericFirstCopyWithImpl<T, \$Res>;'));
55+
expect(content, contains('= _\$SealedGenericSecondCopyWithImpl<T, \$Res>;'));
56+
}
57+
// If content is empty, the first test's error check is sufficient
58+
// as it confirms the generated code compiles without errors
59+
});
60+
}

0 commit comments

Comments
 (0)