Skip to content

Commit f2c7724

Browse files
committed
fix: handle super fields in copyWith, toString, == and hashCode correctly
1 parent 86970aa commit f2c7724

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed

packages/freezed/lib/src/models.dart

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,47 @@ class Class {
697697
library: library,
698698
);
699699

700+
final probeConstructors = ConstructorDetails.parseAll(
701+
declaration,
702+
configs,
703+
globalConfigs: globalConfigs,
704+
unitsExcludingGeneratedFiles: unitsExcludingGeneratedFiles,
705+
);
706+
707+
if (probeConstructors.isEmpty) {
708+
final mergedProps = _collectMergedPropsForPrecheck(
709+
declaration,
710+
configs,
711+
globalConfigs,
712+
unitsExcludingGeneratedFiles,
713+
);
714+
final target = declaration.copyWithTarget;
715+
if (target != null) {
716+
final cloneableNames = {
717+
for (final p in mergedProps.cloneableProperties) p.name,
718+
};
719+
for (final parameter in target.parameters.parameters) {
720+
if (parameter.isOptional) continue;
721+
final paramName = parameter.name?.lexeme;
722+
if (paramName == null) continue;
723+
if (!cloneableNames.contains(paramName)) {
724+
throw InvalidGenerationSourceError(
725+
'''
726+
The class ${declaration.name.lexeme} requested a copyWith implementation, yet the parameter `$paramName` is not cloneable.
727+
728+
To fix, either:
729+
- Disable copyWith using @Freezed(copyWith: false)
730+
- Make `$paramName` optional
731+
- Make sure `this.$paramName` is accessible from the copyWith method
732+
''',
733+
element: declaration.declaredFragment?.element,
734+
node: declaration,
735+
);
736+
}
737+
}
738+
}
739+
}
740+
700741
return Class._from(
701742
declaration,
702743
configs,
@@ -1198,6 +1239,109 @@ To fix, either:
11981239

11991240
return '$escapedElementName$generics';
12001241
}
1242+
1243+
static PropertyList _collectMergedPropsForPrecheck(
1244+
ClassDeclaration declaration,
1245+
ClassConfig configs,
1246+
Freezed globalConfigs,
1247+
List<CompilationUnit> unitsExcludingGeneratedFiles,
1248+
) {
1249+
final props = PropertyList();
1250+
final userLibrary = declaration.declaredFragment!.element.library2;
1251+
1252+
final localConstructors = ConstructorDetails.parseAll(
1253+
declaration,
1254+
configs,
1255+
globalConfigs: globalConfigs,
1256+
unitsExcludingGeneratedFiles: unitsExcludingGeneratedFiles,
1257+
);
1258+
1259+
props.readableProperties.addAll(
1260+
_computeReadableProperties(declaration, localConstructors),
1261+
);
1262+
props.cloneableProperties.addAll(
1263+
_computeCloneableProperties(
1264+
declaration,
1265+
localConstructors,
1266+
configs,
1267+
).where(
1268+
(cloneable) =>
1269+
props.readableProperties.any((e) => e.name == cloneable.name),
1270+
),
1271+
);
1272+
1273+
final seenReadable = {for (final p in props.readableProperties) p.name};
1274+
final seenCloneable = {for (final p in props.cloneableProperties) p.name};
1275+
1276+
var superName = declaration.extendsClause?.superclass.name2.lexeme;
1277+
while (superName != null) {
1278+
final parentDecl = _findClassDeclaration(
1279+
unitsExcludingGeneratedFiles,
1280+
superName,
1281+
);
1282+
if (parentDecl == null) break;
1283+
1284+
final parentConstructors = ConstructorDetails.parseAll(
1285+
parentDecl,
1286+
configs,
1287+
globalConfigs: globalConfigs,
1288+
unitsExcludingGeneratedFiles: unitsExcludingGeneratedFiles,
1289+
);
1290+
1291+
final parentProps = PropertyList();
1292+
parentProps.readableProperties.addAll(
1293+
_computeReadableProperties(parentDecl, parentConstructors),
1294+
);
1295+
parentProps.cloneableProperties.addAll(
1296+
_computeCloneableProperties(
1297+
parentDecl,
1298+
parentConstructors,
1299+
configs,
1300+
).where(
1301+
(cloneable) => parentProps.readableProperties.any(
1302+
(e) => e.name == cloneable.name,
1303+
),
1304+
),
1305+
);
1306+
1307+
final ownerLibrary = parentDecl.declaredFragment!.element.library2;
1308+
1309+
for (final sp in parentProps.readableProperties) {
1310+
if (!_isAccessible(sp.name, ownerLibrary, userLibrary)) continue;
1311+
if (seenReadable.add(sp.name)) {
1312+
props.readableProperties.add(
1313+
sp.copyWith(originClass: parentDecl.name.lexeme),
1314+
);
1315+
}
1316+
}
1317+
for (final sp in parentProps.cloneableProperties) {
1318+
if (!_isAccessible(sp.name, ownerLibrary, userLibrary)) continue;
1319+
if (seenCloneable.add(sp.name)) {
1320+
props.cloneableProperties.add(
1321+
sp.copyWith(originClass: parentDecl.name.lexeme),
1322+
);
1323+
}
1324+
}
1325+
1326+
superName = parentDecl.extendsClause?.superclass.name2.lexeme;
1327+
}
1328+
1329+
return props;
1330+
}
1331+
1332+
static ClassDeclaration? _findClassDeclaration(
1333+
List<CompilationUnit> units,
1334+
String name,
1335+
) {
1336+
for (final unit in units) {
1337+
for (final declaration in unit.declarations) {
1338+
if (declaration is ClassDeclaration && declaration.name.lexeme == name) {
1339+
return declaration;
1340+
}
1341+
}
1342+
}
1343+
return null;
1344+
}
12011345
}
12021346

12031347
class PropertyList {

0 commit comments

Comments
 (0)