Skip to content

Commit 25a0b5b

Browse files
[interop] Add support for Conditional and Predicate Types (#468)
Fixes #464 Fixes #463 This PR adds support for transforming conditional types (`t extends a ? b : c`) and predicate types (`t is a`), and generates Dart alternatives to these types. Predicate types are converted to booleans, while conditional types are converted to unions, since the result can either be one or the other. There currently isn't a way (afaik) to introspect into parameter values to give a specific result depending on the param result. We could be smart, however, and wrap such methods with other methods to do the casting for us in the case of conditional types, and could _try_ to do similar in predicate types (via `assert` checks and more). Signed-off-by: Nike Okoronkwo <nikechukwu@gmail.com>
1 parent e2daa3a commit 25a0b5b

File tree

6 files changed

+128
-56
lines changed

6 files changed

+128
-56
lines changed

web_generator/lib/src/ast/helpers.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ Type desugarTypeAliases(Type t) {
259259
if (t case final ReferredType ref
260260
when ref.declaration is TypeAliasDeclaration) {
261261
return desugarTypeAliases((ref.declaration as TypeAliasDeclaration).type);
262+
} else if (t case ReferredDeclarationType(type: final actualType)) {
263+
return desugarTypeAliases(actualType);
262264
}
263265
return t;
264266
}

web_generator/lib/src/ast/types.dart

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -673,48 +673,48 @@ sealed class _UnionOrIntersectionDeclaration extends NamedDeclaration
673673
dartTypeName ?? typeName,
674674
_ => t.dartName ?? t.id.name
675675
};
676+
final Expression body;
677+
if (desugarTypeAliases(t) == repType) {
678+
body = refer('_');
679+
} else if (jsTypeAlt.id == t.id) {
680+
body = refer('_').asA(type);
681+
} else {
682+
body = switch (t) {
683+
BuiltinType(name: final n) when n == 'int' => refer('_')
684+
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
685+
.property('toDartInt'),
686+
BuiltinType(name: final n) when n == 'double' || n == 'num' =>
687+
refer('_')
688+
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
689+
.property('toDartDouble'),
690+
BuiltinType() => refer('_')
691+
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
692+
.property('toDart'),
693+
ReferredType(
694+
declaration: final decl,
695+
name: final n,
696+
url: final url
697+
)
698+
when decl is EnumDeclaration =>
699+
refer(n, url).property('_').call([
700+
refer('_')
701+
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
702+
.property(decl.baseType is NamedType
703+
? switch ((decl.baseType as NamedType).name) {
704+
'int' => 'toDartInt',
705+
'num' || 'double' => 'toDartDouble',
706+
_ => 'toDart'
707+
}
708+
: 'toDart')
709+
]),
710+
_ => refer('_').asA(jsTypeAlt.emit(options?.toTypeOptions()))
711+
};
712+
}
676713
m
677714
..type = MethodType.getter
678715
..name = 'as${uppercaseFirstLetter(word)}'
679716
..returns = type
680-
..body = jsTypeAlt.id == t.id
681-
? refer('_').asA(type).code
682-
: switch (t) {
683-
BuiltinType(name: final n) when n == 'int' => refer('_')
684-
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
685-
.property('toDartInt')
686-
.code,
687-
BuiltinType(name: final n)
688-
when n == 'double' || n == 'num' =>
689-
refer('_')
690-
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
691-
.property('toDartDouble')
692-
.code,
693-
BuiltinType() => refer('_')
694-
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
695-
.property('toDart')
696-
.code,
697-
ReferredType(
698-
declaration: final decl,
699-
name: final n,
700-
url: final url
701-
)
702-
when decl is EnumDeclaration =>
703-
refer(n, url).property('_').call([
704-
refer('_')
705-
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
706-
.property(decl.baseType is NamedType
707-
? switch ((decl.baseType as NamedType).name) {
708-
'int' => 'toDartInt',
709-
'num' || 'double' => 'toDartDouble',
710-
_ => 'toDart'
711-
}
712-
: 'toDart')
713-
]).code,
714-
_ => refer('_')
715-
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
716-
.code
717-
};
717+
..body = body.code;
718718
});
719719
})));
720720
}

web_generator/lib/src/interop_gen/transform/transformer.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,42 @@ class Transformer {
11131113

11141114
return _getTypeFromTypeNode(refType,
11151115
typeArg: typeArg, isNullable: isNullable ?? false);
1116+
case TSSyntaxKind.TypePredicate:
1117+
// in the future, we can be smarter about this
1118+
// but for now, we just have this as a boolean
1119+
return BuiltinType.primitiveType(PrimitiveType.boolean,
1120+
isNullable: isNullable);
1121+
case TSSyntaxKind.ConditionalType:
1122+
final conditionalType = type as TSConditionalTypeNode;
1123+
final trueType = _transformType(conditionalType.trueType);
1124+
final falseType = _transformType(conditionalType.falseType);
1125+
1126+
final types = [trueType, falseType]
1127+
.sorted((a, b) => a.id.toString().compareTo(b.id.toString()));
1128+
1129+
final expectedID = ID(type: 'type', name: types.join('|'));
1130+
1131+
if (typeMap.containsKey(expectedID.toString())) {
1132+
return (typeMap[expectedID.toString()] as UnionType)
1133+
..isNullable = (isNullable ?? false);
1134+
}
1135+
1136+
final trueTypeName = trueType is NamedType
1137+
? trueType.name
1138+
: trueType.dartName ?? trueType.id.name;
1139+
1140+
final falseTypeName = falseType is NamedType
1141+
? falseType.name
1142+
: falseType.dartName ?? falseType.id.name;
1143+
final conditionalName = '${trueTypeName}Or$falseTypeName';
1144+
1145+
final un = UnionType(types: types, name: conditionalName);
1146+
final unType = typeMap.putIfAbsent(expectedID.toString(), () {
1147+
namer.markUsed(conditionalName);
1148+
return un;
1149+
});
1150+
1151+
return unType..isNullable = (isNullable ?? false);
11161152
case TSSyntaxKind.TypeLiteral:
11171153
// type literal
11181154
final typeLiteralNode = type as TSTypeLiteralNode;

web_generator/lib/src/js/typescript.types.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ extension type const TSSyntaxKind._(num _) {
100100
static const TSSyntaxKind FunctionType = TSSyntaxKind._(184);
101101
static const TSSyntaxKind ConstructorType = TSSyntaxKind._(185);
102102
static const TSSyntaxKind TypeOperator = TSSyntaxKind._(198);
103+
static const TSSyntaxKind TypePredicate = TSSyntaxKind._(182);
104+
static const TSSyntaxKind ConditionalType = TSSyntaxKind._(194);
103105

104106
// Other
105107
static const TSSyntaxKind Identifier = TSSyntaxKind._(80);
@@ -202,6 +204,24 @@ extension type TSParenthesizedTypeNode._(JSObject _) implements TSTypeNode {
202204
external TSTypeNode get type;
203205
}
204206

207+
@JS('TypePredicateNode')
208+
extension type TSTypePredicateNode._(JSObject _) implements TSTypeNode {
209+
@redeclare
210+
TSSyntaxKind get kind => TSSyntaxKind.TypePredicate;
211+
external TSIdentifier get parameterName;
212+
external TSTypeNode? get type;
213+
}
214+
215+
@JS('ConditionalTypeNode')
216+
extension type TSConditionalTypeNode._(JSObject _) implements TSTypeNode {
217+
@redeclare
218+
TSSyntaxKind get kind => TSSyntaxKind.ConditionalType;
219+
external TSTypeNode get checkType;
220+
external TSTypeNode get extendsType;
221+
external TSTypeNode get trueType;
222+
external TSTypeNode get falseType;
223+
}
224+
205225
@JS('TupleTypeNode')
206226
extension type TSTupleTypeNode._(JSObject _) implements TSTypeNode {
207227
external TSNodeArray<TSTypeNode> get elements;

web_generator/test/integration/interop_gen/ts_typing_expected.dart

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ external String myFunction(String param);
1515
@_i1.JS()
1616
external String myEnclosingFunction(_i1.JSFunction func);
1717
@_i1.JS()
18+
external bool objectIsProduct(_i1.JSObject obj);
19+
@_i1.JS()
20+
external AnonymousType_2194029 get randomNonTypedProduct;
21+
@_i1.JS()
22+
external ProductOrrandomNonTypedProduct objectAsProduct(
23+
_i1.JSObject obj,
24+
bool structured,
25+
);
26+
@_i1.JS()
1827
external _i1.JSArray<AnonymousType_9143117<T>>
1928
indexedArray<T extends _i1.JSAny?>(_i1.JSArray<T> arr);
2029
@_i1.JS()
@@ -84,8 +93,6 @@ external _i2.JSTuple4<_i1.JSString, _i1.JSNumber, _i1.JSBoolean, _i1.JSSymbol>
8493
@_i1.JS()
8594
external AnonymousUnion_7503220 get eightOrSixteen;
8695
@_i1.JS()
87-
external AnonymousType_2194029 get randomNonTypedProduct;
88-
@_i1.JS()
8996
external AnonymousType_1358595 get config;
9097
extension type MyProduct._(_i1.JSObject _) implements Product {
9198
external MyProduct(
@@ -125,6 +132,26 @@ external _i1.JSAny? get someIntersection;
125132
external AnonymousIntersection_4895242 get myThirdIntersection;
126133
@_i1.JS()
127134
external AnonymousIntersection_1711585 get myTypeGymnastic;
135+
extension type AnonymousType_2194029._(_i1.JSObject _) implements _i1.JSObject {
136+
external AnonymousType_2194029({
137+
double id,
138+
String name,
139+
double price,
140+
});
141+
142+
external double id;
143+
144+
external String name;
145+
146+
external double price;
147+
}
148+
typedef Product = AnonymousType_2194029;
149+
extension type ProductOrrandomNonTypedProduct._(AnonymousType_2194029 _)
150+
implements AnonymousType_2194029 {
151+
Product get asProduct => _;
152+
153+
AnonymousType_2194029 get asRandomNonTypedProduct => _;
154+
}
128155
extension type AnonymousType_9143117<T extends _i1.JSAny?>._(_i1.JSObject _)
129156
implements _i1.JSObject {
130157
external AnonymousType_9143117({
@@ -210,19 +237,6 @@ extension type AnonymousUnion_7503220._(_i1.JSTypedArray _)
210237

211238
_i1.JSUint16Array get asJSUint16Array => (_ as _i1.JSUint16Array);
212239
}
213-
extension type AnonymousType_2194029._(_i1.JSObject _) implements _i1.JSObject {
214-
external AnonymousType_2194029({
215-
double id,
216-
String name,
217-
double price,
218-
});
219-
220-
external double id;
221-
222-
external String name;
223-
224-
external double price;
225-
}
226240
extension type AnonymousType_1358595._(_i1.JSObject _) implements _i1.JSObject {
227241
external AnonymousType_1358595({
228242
double discountRate,
@@ -233,7 +247,6 @@ extension type AnonymousType_1358595._(_i1.JSObject _) implements _i1.JSObject {
233247

234248
external double taxRate;
235249
}
236-
typedef Product = AnonymousType_2194029;
237250
extension type AnonymousType_2773310._(_i1.JSObject _) implements _i1.JSObject {
238251
external AnonymousType_2773310({
239252
String id,

web_generator/test/integration/interop_gen/ts_typing_input.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export declare const myEnumValue2: typeof MyEnum;
2929
export declare function myFunction(param: string): string;
3030
export declare let myFunctionAlias: typeof myFunction;
3131
export declare let myFunctionAlias2: typeof myFunctionAlias;
32-
// export declare let myPreClone: typeof myComposedType;
3332
export declare function myEnclosingFunction(func: typeof myFunction): string;
3433
export declare const myEnclosingFunctionAlias: typeof myEnclosingFunction;
3534
export declare const myComposedType: ComposedType;
@@ -65,7 +64,9 @@ export declare class MyProduct implements Product {
6564
price: number;
6665
constructor(id: number, name: string, price: number);
6766
}
68-
export function indexedArray<T>(arr: T[]): { id: number, value: T }[];
67+
export declare function objectIsProduct(obj: object): obj is Product;
68+
export declare function objectAsProduct(obj: object, structured: boolean): (typeof structured) extends true ? Product : typeof randomNonTypedProduct;
69+
export declare function indexedArray<T>(arr: T[]): { id: number, value: T }[];
6970
export const responseObject: {
7071
id: string;
7172
value: any;

0 commit comments

Comments
 (0)