Skip to content

Commit 4d44abe

Browse files
committed
refactor: limited support for non qualified type trees
1 parent 4168fe3 commit 4d44abe

File tree

9 files changed

+143
-83
lines changed

9 files changed

+143
-83
lines changed

packages/dogs/lib/src/converters/polymorphic.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class PolymorphicConverter extends DogConverter with OperationMapMixin {
7575
final simpleValue = value[codec.valueDiscriminator]!;
7676
return operation.deserialize(simpleValue, engine);
7777
} else {
78-
final clone = Map.of(value);
78+
final clone = Map<String,dynamic>.from(value);
7979
clone.remove(codec.typeDiscriminator);
8080
return operation.deserialize(clone, engine);
8181
}

packages/dogs/lib/src/engine.dart

+24-1
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ class DogEngine with MetadataMixin {
359359
/// polymorphic converters if any type tree terminals are not concrete.
360360
DogConverter getTreeConverter(TypeTree tree, [bool allowPolymorphic = true]) {
361361
if (!tree.isQualified) {
362-
throw DogException("TypeTree '$tree' must be qualified");
362+
return _getAnonymousTreeConverter(tree, allowPolymorphic);
363363
}
364364

365365
final cachedConverter =
@@ -370,6 +370,29 @@ class DogEngine with MetadataMixin {
370370
return created;
371371
}
372372

373+
DogConverter<dynamic> _getAnonymousTreeConverter(TypeTree<dynamic> tree, [bool allowPolymorphic = true]) {
374+
if (tree.isTerminal) {
375+
if (codec.isNative(tree.base.typeArgument)) {
376+
return codec.bridgeConverters[tree.base.typeArgument]!;
377+
}
378+
379+
final associated = findAssociatedConverter(tree.base.typeArgument);
380+
if (associated != null) return associated;
381+
if (allowPolymorphic) {
382+
return TreeBaseConverterFactory.polymorphicConverter;
383+
}
384+
throw DogException(
385+
"No type tree converter for tree ${tree.qualified} found. (Polymorphism disabled)");
386+
} else {
387+
// Use factory
388+
final factory = _treeBaseFactories[tree.base.typeArgument];
389+
if (factory == null) {
390+
throw DogException("No type tree converter for ${tree.base} found");
391+
}
392+
return factory.getConverter(tree, this, allowPolymorphic);
393+
}
394+
}
395+
373396
DogConverter<dynamic> _getTreeConverterUncached(TypeTree<dynamic> tree,
374397
[bool allowPolymorphic = true]) {
375398
if (tree.isTerminal) {

packages/dogs/lib/src/extensions.dart

-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ extension DogEngineShortcuts on DogEngine {
121121
dynamic result;
122122

123123
if (tree != null) {
124-
if (!tree.isQualified) throw DogException("TypeTree must be qualified");
125124
final converter = getTreeConverter(tree);
126125
result = modeRegistry.nativeSerialization
127126
.forConverter(converter, this)
@@ -148,7 +147,6 @@ extension DogEngineShortcuts on DogEngine {
148147
}
149148

150149
if (tree != null) {
151-
if (!tree.isQualified) throw DogException("TypeTree must be qualified");
152150
final converter = getTreeConverter(tree);
153151
return modeRegistry.nativeSerialization
154152
.forConverter(converter, this)

packages/dogs/lib/src/structure/field.dart

+6-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,12 @@ class DogStructureField extends RetainedAnnotationHolder
6565
if (iterableKind == IterableKind.none) {
6666
return type.qualified;
6767
} else {
68-
return type.arguments[0].qualified;
68+
final arg = type.arguments[0];
69+
if (arg.isQualified) {
70+
return arg.qualified;
71+
} else {
72+
return arg.base;
73+
}
6974
}
7075
}
7176

packages/dogs/lib/src/trees.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ abstract class TreeBaseConverterFactory {
3939
/// Returns the converter for a [tree].
4040
static DogConverter treeConverter(
4141
TypeTree tree, DogEngine engine, bool allowPolymorphic) {
42-
if (tree.isTerminal && tree.qualified.typeArgument == dynamic) {
42+
if (tree.isTerminal && tree.base.typeArgument == dynamic) {
4343
return polymorphicConverter;
4444
}
4545
return engine.getTreeConverter(tree, allowPolymorphic);

packages/dogs/lib/src/trees/iterables.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ mixin IterableTreeBaseConverterMixin on DogConverter {
118118

119119
/// Creates a new value from the given [entries].
120120
dynamic create(Iterable entries) =>
121-
itemSubtree.qualified.consumeTypeArg(iterableCreator, entries);
121+
itemSubtree.qualifiedOrBase.consumeTypeArg(iterableCreator, entries);
122122

123123
/// Destructs the given [value] into an iterable.
124124
Iterable destruct(dynamic value) =>
125-
itemSubtree.qualified.consumeTypeArg(iterableDestructor, value);
125+
itemSubtree.qualifiedOrBase.consumeTypeArg(iterableDestructor, value);
126126

127127
@override
128128
OperationMode<dynamic>? resolveOperationMode(Type opmodeType) {

packages/dogs/lib/src/trees/nargs.dart

+23-24
Original file line numberDiff line numberDiff line change
@@ -25,43 +25,43 @@ class _NargsTreeBaseConverterFactory<BASE> extends TreeBaseConverterFactory {
2525
@override
2626
DogConverter getConverter(
2727
TypeTree tree, DogEngine engine, bool allowPolymorphic) {
28-
final argumentConverters = TreeBaseConverterFactory.argumentConverters(
29-
tree, engine, allowPolymorphic);
3028
if (tree.arguments.length != nargs) {
3129
throw ArgumentError("Expected $nargs type arguments");
3230
}
31+
final argumentConverters = TreeBaseConverterFactory.argumentConverters(
32+
tree, engine, allowPolymorphic);
3333
final factory = switch (tree.arguments.length) {
34-
1 => tree.arguments.first.qualified
34+
1 => tree.arguments.first.qualifiedOrBase
3535
.consumeType(captureFactory as Function<_>()),
3636
2 => TypeContainers.arg2(
37-
tree.arguments[0].qualified,
38-
tree.arguments[1].qualified,
37+
tree.arguments[0].qualifiedOrBase,
38+
tree.arguments[1].qualifiedOrBase,
3939
).consume(captureFactory as Function<_0, _1>()),
4040
3 => TypeContainers.arg3(
41-
tree.arguments[0].qualified,
42-
tree.arguments[1].qualified,
43-
tree.arguments[2].qualified,
41+
tree.arguments[0].qualifiedOrBase,
42+
tree.arguments[1].qualifiedOrBase,
43+
tree.arguments[2].qualifiedOrBase,
4444
).consume(captureFactory as Function<_0, _1, _2>()),
4545
4 => TypeContainers.arg4(
46-
tree.arguments[0].qualified,
47-
tree.arguments[1].qualified,
48-
tree.arguments[2].qualified,
49-
tree.arguments[3].qualified,
46+
tree.arguments[0].qualifiedOrBase,
47+
tree.arguments[1].qualifiedOrBase,
48+
tree.arguments[2].qualifiedOrBase,
49+
tree.arguments[3].qualifiedOrBase,
5050
).consume(captureFactory as Function<_0, _1, _2, _3>()),
5151
5 => TypeContainers.arg5(
52-
tree.arguments[0].qualified,
53-
tree.arguments[1].qualified,
54-
tree.arguments[2].qualified,
55-
tree.arguments[3].qualified,
56-
tree.arguments[4].qualified,
52+
tree.arguments[0].qualifiedOrBase,
53+
tree.arguments[1].qualifiedOrBase,
54+
tree.arguments[2].qualifiedOrBase,
55+
tree.arguments[3].qualifiedOrBase,
56+
tree.arguments[4].qualifiedOrBase,
5757
).consume(captureFactory as Function<_0, _1, _2, _3, _4>()),
5858
6 => TypeContainers.arg6(
59-
tree.arguments[0].qualified,
60-
tree.arguments[1].qualified,
61-
tree.arguments[2].qualified,
62-
tree.arguments[3].qualified,
63-
tree.arguments[4].qualified,
64-
tree.arguments[5].qualified,
59+
tree.arguments[0].qualifiedOrBase,
60+
tree.arguments[1].qualifiedOrBase,
61+
tree.arguments[2].qualifiedOrBase,
62+
tree.arguments[3].qualifiedOrBase,
63+
tree.arguments[4].qualifiedOrBase,
64+
tree.arguments[5].qualifiedOrBase,
6565
).consume(captureFactory as Function<_0, _1, _2, _3, _4, _5>()),
6666
int() => throw Exception("Too many type arguments")
6767
};
@@ -94,7 +94,6 @@ class _NTreeArgConverterImpl<BASE> extends DogConverter<BASE>
9494
SchemaType describeOutput(DogEngine engine, SchemaConfig config) {
9595
return delegate.inferSchemaType(engine, config);
9696
}
97-
9897
}
9998

10099
/// A converter interface for a generic type with a fixed number of type arguments.

packages/dogs/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ environment:
1616

1717
dependencies:
1818
collection: ^1.19.1
19-
lyell: ^1.2.0
19+
lyell: ^1.3.1
2020
meta: ^1.16.0
2121

2222
dev_dependencies:

smoke/test0/lib/parts/models.dart

+85-50
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,18 @@ void testModels() {
2525
testSingleModel<ModelF>(ModelF.variant0, ModelF.variant1);
2626
testSingleModel<ModelG>(ModelG.variant0, ModelG.variant1);
2727
testSingleModel<Note>(Note.variant0, Note.variant1);
28-
testSingleModel<DeepPolymorphic>(DeepPolymorphic.variant0, DeepPolymorphic.variant1);
29-
testSingleModel<CustomBaseImpl>(CustomBaseImpl.variant0, CustomBaseImpl.variant1);
30-
testSingleModel<InitializersModel>(InitializersModel.variant0, InitializersModel.variant1);
31-
testSingleModel<ConstructorBodyModel>(ConstructorBodyModel.variant0, ConstructorBodyModel.variant1);
28+
testSingleModel<DeepPolymorphic>(
29+
DeepPolymorphic.variant0, DeepPolymorphic.variant1);
30+
testSingleModel<CustomBaseImpl>(
31+
CustomBaseImpl.variant0, CustomBaseImpl.variant1);
32+
testSingleModel<InitializersModel>(
33+
InitializersModel.variant0, InitializersModel.variant1);
34+
testSingleModel<ConstructorBodyModel>(
35+
ConstructorBodyModel.variant0, ConstructorBodyModel.variant1);
3236
testSingleModel<GetterModel>(GetterModel.variant0, GetterModel.variant1);
33-
testSingleModel<DefaultValueModel>(DefaultValueModel.variant0, DefaultValueModel.variant1);
34-
37+
testSingleModel<DefaultValueModel>(
38+
DefaultValueModel.variant0, DefaultValueModel.variant1);
39+
3540
test("Default Values", () {
3641
var defaultValues = DefaultValueModel.variant0();
3742
var defaultMap = dogs.toNative(defaultValues) as Map;
@@ -46,61 +51,55 @@ void testModels() {
4651
}
4752

4853
void testSingleModel<T>(T Function() a, T Function() b) => group("$T", () {
54+
var va0 = a();
55+
var va1 = a();
56+
var vb0 = b();
57+
var vb1 = b();
4958

50-
SchemaPass.run((pass) {
51-
print(dogs.findAssociatedConverter(T)!.describeOutput(dogs, SchemaConfig()).toJson());
52-
});
53-
54-
var va0 = a();
55-
var va1 = a();
56-
var vb0 = b();
57-
var vb1 = b();
59+
test("Base", () {
60+
var ea = dogs.toJson<T>(va0);
61+
var eb = dogs.toJson<T>(vb0);
62+
var da = dogs.fromJson<T>(ea);
63+
var db = dogs.fromJson<T>(eb);
64+
expect(va1, da, reason: "Non-pure serialization");
65+
expect(va0, da, reason: "Non-pure serialization");
66+
expect(vb1, db, reason: "Non-pure serialization");
67+
expect(vb0, db, reason: "Non-pure serialization");
68+
expect(ea, isNot(eb), reason: "Wrong equality");
69+
});
5870

59-
test("Base", () {
60-
var ea = dogs.toJson<T>(va0);
61-
var eb = dogs.toJson<T>(vb0);
62-
var da = dogs.fromJson<T>(ea);
63-
var db = dogs.fromJson<T>(eb);
64-
expect(va1, da, reason: "Non-pure serialization");
65-
expect(va0, da, reason: "Non-pure serialization");
66-
expect(vb1, db, reason: "Non-pure serialization");
67-
expect(vb0, db, reason: "Non-pure serialization");
68-
expect(ea, isNot(eb), reason: "Wrong equality");
69-
});
71+
// Test Iterable kind based serialization
72+
group("Kind", () {
73+
test("List", () => _testListKind<T>(va0, va1, vb0, vb1));
74+
test("Set", () => _testSetKind<T>(va0, va1, vb0, vb1));
75+
});
7076

71-
// Test Iterable kind based serialization
72-
group("Kind", () {
73-
test("List", () => _testListKind<T>(va0, va1, vb0, vb1));
74-
test("Set", () => _testSetKind<T>(va0, va1, vb0, vb1));
75-
});
76-
77-
group("Type Tree", () {
78-
test("Map", () => _testMap<T>(va0, va1));
79-
test("Optional A", () => _testOptional<T>(va0));
80-
test("Optional B", () => _testOptional<T>(va1));
81-
});
82-
});
77+
group("Type Tree", () {
78+
test("Map", () => _testMap<T>(va0, va1));
79+
test("Optional A", () => _testOptional<T>(va0));
80+
test("Optional B", () => _testOptional<T>(va1));
81+
test("Runtime Type Tree A", () => _testRuntimeTypeTree<T>(va0));
82+
test("Runtime Type Tree B", () => _testRuntimeTypeTree<T>(va1));
83+
test(
84+
"Deep Runtime Type Tree A", () => _testDeepRuntimeTypeTree<T>(va0));
85+
test(
86+
"Deep Runtime Type Tree B", () => _testDeepRuntimeTypeTree<T>(va1));
87+
});
88+
});
8389

8490
void _testListKind<T>(T va0, T va1, T vb0, T vb1) {
8591
var list = [va0, va1, vb0, vb1];
86-
var encodedList = dogs.toJson(list,
87-
type: T, kind: IterableKind.list
88-
);
89-
var decodedList = dogs.fromJson(encodedList,
90-
type: T, kind: IterableKind.list
91-
);
92+
var encodedList = dogs.toJson(list, type: T, kind: IterableKind.list);
93+
var decodedList =
94+
dogs.fromJson(encodedList, type: T, kind: IterableKind.list);
9295
expect(decodedList, orderedEquals(list));
9396
}
9497

9598
void _testSetKind<T>(T va0, T va1, T vb0, T vb1) {
9699
var list = {va0, va1, vb0, vb1};
97100
expect(list, hasLength(2));
98-
var encodedList = dogs.toJson(list,
99-
type: T, kind: IterableKind.set
100-
);
101-
var decodedList = dogs.fromJson(encodedList,
102-
type: T, kind: IterableKind.set
103-
);
101+
var encodedList = dogs.toJson(list, type: T, kind: IterableKind.set);
102+
var decodedList = dogs.fromJson(encodedList, type: T, kind: IterableKind.set);
104103
expect(decodedList, orderedEquals(list));
105104
}
106105

@@ -121,8 +120,44 @@ void _testOptional<T>(T val) {
121120
var tree = QualifiedTypeTree.arg1<Optional<T>, Optional, T>();
122121
var ea = dogs.toJson(Optional(va0), tree: tree);
123122
var eb = dogs.toJson(Optional(va1), tree: tree);
123+
var p0 = jsonDecode(ea);
124+
var p1 = jsonDecode(eb);
124125
var da = dogs.fromJson<Optional<T>>(ea, tree: tree);
125126
var db = dogs.fromJson<Optional<T>>(eb, tree: tree);
126127
expect(va0, da.value, reason: "Non-pure serialization");
127128
expect(va1, db.value, reason: "Non-pure serialization");
128-
}
129+
expect(p0, isA<Map>());
130+
expect(p1, isNull);
131+
}
132+
133+
void _testRuntimeTypeTree<T>(T val) {
134+
var va0 = val;
135+
final tree = TypeTreeN<Map>([
136+
TypeTree.$string,
137+
TypeTreeN<List>([UnsafeRuntimeTypeCapture(T)])
138+
]);
139+
var ea = dogs.toJson({
140+
"a": [va0]
141+
}, tree: tree);
142+
var payload = jsonDecode(ea);
143+
var da = dogs.fromJson(ea, tree: tree);
144+
expect(va0, da["a"][0], reason: "Non-pure serialization");
145+
expect((payload["a"][0] as Map).containsKey("_type"), false,
146+
reason: "Polymorphic serialization where not required");
147+
}
148+
149+
void _testDeepRuntimeTypeTree<T>(T val) {
150+
var va0 = val;
151+
final tree = UnsafeRuntimeTypeCapture(Map, arguments: [
152+
UnsafeRuntimeTypeCapture(String),
153+
UnsafeRuntimeTypeCapture(List, arguments: [UnsafeRuntimeTypeCapture(T)])
154+
]);
155+
var ea = dogs.toJson({
156+
"a": [va0]
157+
}, tree: tree);
158+
var payload = jsonDecode(ea);
159+
var da = dogs.fromJson(ea, tree: tree);
160+
expect(va0, da["a"][0], reason: "Non-pure serialization");
161+
expect((payload["a"][0] as Map).containsKey("_type"), false,
162+
reason: "Polymorphic serialization where not required");
163+
}

0 commit comments

Comments
 (0)