Skip to content

Commit 89e2658

Browse files
authored
Private unions (#1159)
1 parent 92b563f commit 89e2658

File tree

5 files changed

+41
-36
lines changed

5 files changed

+41
-36
lines changed

packages/freezed/CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,19 @@ This feature offers fine-grained control over every parts of your models.
161161
Unfortunately, it is kind of required to "extend" the parent class (so here `extends Result<T>`). This is because Dart doesn't support `sealed mixin class`, so you can't do
162162
`with Result<T>` instead.
163163

164+
### New: private unions
165+
166+
It is now possible to have a private constructor for unions:
167+
168+
```dart
169+
@freezed
170+
sealed class Result<T> with _$Result {
171+
// It wasn't possible to write _data before, but now is.
172+
factory Result._data(T data) = ResultData;
173+
factory Result.error(Object error) = ResultError;
174+
}
175+
```
176+
164177
## 2.5.8 - 2025-01-06
165178

166179
Support analyzer 7.0.0

packages/freezed/lib/src/models.dart

+12-15
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,15 @@ class ConstructorDetails {
195195
);
196196

197197
if (constructor.factoryKeyword == null &&
198-
constructor.name?.lexeme != '_' &&
198+
!constructor.isManualCtor &&
199199
freezedCtors.isNotEmpty) {
200200
throw InvalidGenerationSourceError(
201201
'Classes decorated with @freezed can only have a single non-factory constructor.',
202202
element: constructor.declaredElement,
203203
);
204204
}
205205

206-
if (constructor.name?.lexeme == '_') {
206+
if (constructor.isManualCtor) {
207207
for (final param in constructor.parameters.parameters) {
208208
if (param.isPositional) {
209209
for (final ctor in freezedCtors) {
@@ -259,9 +259,7 @@ When specifying fields in non-factory constructor then specifying factory constr
259259
}) {
260260
final result = <ConstructorDetails>[];
261261

262-
final manualConstructor = declaration.constructors
263-
.where((e) => e.name?.lexeme == '_')
264-
.firstOrNull;
262+
final manualConstructor = declaration.manualConstructor;
265263

266264
for (final constructor in declaration.constructors) {
267265
if (constructor.factoryKeyword == null ||
@@ -352,13 +350,6 @@ When specifying fields in non-factory constructor then specifying factory constr
352350
);
353351
}
354352

355-
if (result.length > 1 && result.any((c) => c.name.startsWith('_'))) {
356-
throw InvalidGenerationSourceError(
357-
'A freezed union cannot have private constructors',
358-
element: declaration.declaredElement,
359-
);
360-
}
361-
362353
if (configs.annotation.fallbackUnion != null &&
363354
result.none((c) => c.isFallback)) {
364355
throw InvalidGenerationSourceError(
@@ -524,9 +515,7 @@ class Class {
524515
required Freezed globalConfigs,
525516
required List<CompilationUnit> unitsExcludingGeneratedFiles,
526517
}) {
527-
final privateCtor = declaration.constructors.where((ctor) {
528-
return ctor.name?.lexeme == '_' && ctor.factoryKeyword == null;
529-
}).firstOrNull;
518+
final privateCtor = declaration.manualConstructor;
530519

531520
for (final field in declaration.declaredElement!.fields) {
532521
_assertValidFieldUsage(field, shouldUseExtends: privateCtor != null);
@@ -1147,6 +1136,10 @@ class ClassConfig {
11471136
final Freezed annotation;
11481137
}
11491138

1139+
extension on ConstructorDeclaration {
1140+
bool get isManualCtor => name?.lexeme == '_' && factoryKeyword == null;
1141+
}
1142+
11501143
extension ClassDeclarationX on ClassDeclaration {
11511144
/// Pick either Class(), Class._() or the first constructor found, in that order.
11521145
ConstructorDeclaration? get copyWithTarget {
@@ -1158,6 +1151,10 @@ extension ClassDeclarationX on ClassDeclaration {
11581151
constructors.firstOrNull;
11591152
}
11601153

1154+
ConstructorDeclaration? get manualConstructor {
1155+
return constructors.where((e) => e.isManualCtor).firstOrNull;
1156+
}
1157+
11611158
Iterable<ConstructorDeclaration> get constructors {
11621159
return members.whereType<ConstructorDeclaration>();
11631160
}

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,14 @@ ${copyWith?.concreteImpl(constructor.parameters) ?? ''}
117117
);
118118
}
119119

120-
final superCall = data.superCall!;
120+
final superCall = data.superCall;
121121

122122
// Attempt to use super.field when possible.
123123
// For now, we only do so for named parameters as positional parameters
124124
// are trickier.
125-
if (isNamed && superCall.positional.contains(p.name)) {
125+
if (superCall != null &&
126+
isNamed &&
127+
superCall.positional.contains(p.name)) {
126128
superParameters.add(p.name);
127129
return (
128130
SuperParameter.fromParameter(p),

packages/freezed/test/integration/multiple_constructors.dart

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ import 'package:freezed_annotation/freezed_annotation.dart';
22

33
part 'multiple_constructors.freezed.dart';
44

5+
@freezed
6+
sealed class PrivateUnion with _$PrivateUnion {
7+
// ignore: unused_element
8+
const factory PrivateUnion._(int value) = _PrivateUnion;
9+
// ignore: unused_element
10+
const factory PrivateUnion._b(int value) = _PrivateUnionB;
11+
}
12+
513
@freezed
614
sealed class Response<T> with _$Response<T> {
715
Response._({DateTime? time}) : time = time ?? DateTime(0, 0, 0);

packages/freezed/test/source_gen_src.dart

+4-19
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,6 @@ class _DefaultOnRequiredPositional implements DefaultOnRequiredPositional {
9696
_DefaultOnRequiredPositional(int a);
9797
}
9898

99-
@ShouldThrow('A freezed union cannot have private constructors')
100-
@freezed
101-
abstract class Mixed {
102-
factory Mixed._internal(String a) = Mixed0;
103-
factory Mixed.named(String b) = Mixed1;
104-
}
105-
10699
@ShouldThrow('Fallback union was specified but no fallback constructor exists.')
107100
@Freezed(fallbackUnion: 'fallback')
108101
class FallbackUnionMissing {
@@ -135,24 +128,16 @@ class _MutableProperty implements MutableProperty {
135128
int? a;
136129
}
137130

138-
class Mixed1 implements Mixed {
139-
Mixed1(String b);
140-
}
141-
142-
class Mixed0 implements Mixed {
143-
Mixed0(String a);
144-
}
145-
146131
@ShouldGenerate(
147132
'',
148133
contains: true,
149134
expectedLogItems: [],
150135
)
151136
@freezed
152-
abstract class AstractClass {
153-
const factory AstractClass() = _AstractClass;
137+
abstract class AbstractClass {
138+
const factory AbstractClass() = _AbstractClass;
154139
}
155140

156-
class _AstractClass implements AstractClass {
157-
const _AstractClass();
141+
class _AbstractClass implements AbstractClass {
142+
const _AbstractClass();
158143
}

0 commit comments

Comments
 (0)