Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/**/.DS_Store
/**/*.draft.dart
.vscode
33 changes: 31 additions & 2 deletions packages/draft_builder/lib/src/generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,8 @@ class DraftGenerator extends GeneratorForAnnotation<Draft> {

// Forward computed getters.
final handledNames = fields.map((f) => f.name).toSet();
for (final accessor in classElement.getters.where(
final forwardedGetters = _getForwardedGetters(classElement);
for (final accessor in forwardedGetters.where(
(a) => !a.isSynthetic && !handledNames.contains(a.name),
)) {
final returnType = accessor.returnType.getDisplayString();
Expand All @@ -626,7 +627,8 @@ class DraftGenerator extends GeneratorForAnnotation<Draft> {
buffer.writeln();

// Forward non-static, public instance methods.
for (final method in classElement.methods.where(
final forwardedMethods = _getForwardedMethods(classElement);
for (final method in forwardedMethods.where(
(m) => !m.isStatic && m.isPublic && !m.isOperator && m.name != 'save',
)) {
final returnType = method.returnType.getDisplayString();
Expand Down Expand Up @@ -679,3 +681,30 @@ class DraftGenerator extends GeneratorForAnnotation<Draft> {
return buffer.toString();
}
}

/// Returns unique getters from [element] and supertypes, excluding Object type.
List<GetterElement> _getForwardedGetters(ClassElement element) {
return _distinctByDisplayName([
...element.getters,
for (var supertype in element.allSupertypes)
if (!supertype.isDartCoreObject) ...supertype.getters,
]);
}

/// Returns unique methods from [element] and supertypes, excluding Object type.
List<MethodElement> _getForwardedMethods(ClassElement element) {
return _distinctByDisplayName([
...element.methods,
for (var supertype in element.allSupertypes)
if (!supertype.isDartCoreObject) ...supertype.methods,
]);
}

/// Returns elements with unique display names, keeping the first occurrence.
List<T> _distinctByDisplayName<T extends Element>(List<T> elements) {
final uniqueNames = <String>{};
return [
for (var element in elements)
if (uniqueNames.add(element.displayName)) element,
];
}
39 changes: 39 additions & 0 deletions packages/draft_builder/test/integration/subtyping.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:draft/draft.dart';

part 'subtyping.draft.dart';

sealed class Parent extends Base with Mixin implements Interface {
@override
bool get flagA => true;

@override
void methodA() {}
}

@draft
class Child extends Parent {
Child({required this.value});

final int value;

@override
bool get flagB => false;

@override
void methodB() {}
}

abstract class Base {
bool get flagA;
void methodA();
}

abstract interface class Interface {
bool get flagA;
void methodB();
}

abstract mixin class Mixin {
bool get flagB;
void methodA();
}
53 changes: 53 additions & 0 deletions packages/draft_builder/test/subtyping_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'package:test/test.dart';

import 'common.dart';
import 'integration/subtyping.dart';

void main() {
test('compiles', () async {
await expectLater(
compile(r'''
import 'subtyping.dart';

void main() {
Child(value: 1);
Child(value: 1).draft().save();
Child(value: 1).draft().methodA();
Child(value: 1).draft().methodB();
bool a = Child(value: 1).draft().flagA;
bool b = Child(value: 1).draft().flagB;
Child(value: 1).produce((draft) {
draft.value = 2;
draft.methodA();
draft.methodB();
bool a1 = draft.flagA;
bool b1 = draft.flagB;
});
Child(value: 1).methodA();
Child(value: 1).methodB();
bool a2 = Child(value: 1).flagA;
bool b2 = Child(value: 1).flagB;
}
'''),
completes,
);
});

test('works correctly', () async {
final instance = Child(value: 1);
final draft = instance.draft();
expect(draft.value, 1);
draft.value = 2;
expect(draft.value, 2);
expect(instance.value, 1);
final saved = draft.save();
expect(saved.value, 2);
expect(instance.value, 1);
expect(saved, isNot(same(instance)));
expect(saved, isNot(same(draft)));
expect(saved.flagA, isTrue);
expect(saved.draft().flagA, isTrue);
expect(saved.flagB, isFalse);
expect(saved.draft().flagB, isFalse);
});
}