Skip to content

Commit 354e229

Browse files
[interop] Add Support for Typealiases (#407)
* Interop Gen: Support Typealiases Fixes #406 * Refactored fix for typealias use in type generics: awaiting review * fixed type argument issue: awaiting resolution * added filtering case support for `TypeAliasDeclaration` * Formatting files * Fixed standing issue - Deleted `ts_bindings_test.dart` (ts_bindings_test.dart needs input data to run #372) - transformer.dart updated * Changed dart formatter version to match `web_generator` version * Added support for referring defined types: - Added test cases for enum decls - Added support for getting js types for nested aliases and enums
1 parent f0f0914 commit 354e229

File tree

10 files changed

+238
-92
lines changed

10 files changed

+238
-92
lines changed

web_generator/lib/src/ast/declarations.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,40 @@ class EnumMember {
232232

233233
String? dartName;
234234
}
235+
236+
class TypeAliasDeclaration extends NamedDeclaration
237+
implements ExportableDeclaration {
238+
@override
239+
final String name;
240+
241+
final List<GenericType> typeParameters;
242+
243+
final Type type;
244+
245+
@override
246+
final String? dartName;
247+
248+
@override
249+
bool exported;
250+
251+
@override
252+
ID get id => ID(type: 'typealias', name: name);
253+
254+
TypeAliasDeclaration(
255+
{required this.name,
256+
this.typeParameters = const [],
257+
required this.type,
258+
required this.exported})
259+
: dartName = null;
260+
261+
@override
262+
TypeDef emit([DeclarationOptions? options]) {
263+
options ??= DeclarationOptions();
264+
265+
return TypeDef((t) => t
266+
..name = name
267+
..types
268+
.addAll(typeParameters.map((t) => t.emit(options?.toTypeOptions())))
269+
..definition = type.emit(options?.toTypeOptions()));
270+
}
271+
}

web_generator/lib/src/ast/helpers.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:code_builder/code_builder.dart';
77
import 'base.dart';
88
import 'builtin.dart';
99
import 'declarations.dart';
10+
import 'types.dart';
1011

1112
BuiltinType? getSupportedType(String name, [List<Type> typeParams = const []]) {
1213
final type = switch (name) {
@@ -38,6 +39,15 @@ Type getJSTypeAlternative(Type type) {
3839
if (primitiveType == null) return BuiltinType.anyType;
3940

4041
return BuiltinType.primitiveType(primitiveType, shouldEmitJsType: true);
42+
} else if (type is ReferredType) {
43+
switch (type.declaration) {
44+
case TypeAliasDeclaration(type: final t):
45+
case EnumDeclaration(baseType: final t):
46+
final jsTypeT = getJSTypeAlternative(t);
47+
return jsTypeT == t ? type : jsTypeT;
48+
default:
49+
return type;
50+
}
4151
}
4252
return type;
4353
}

web_generator/lib/src/interop_gen/transform.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class TransformResult {
2323
// (namespaces + functions, multiple interfaces, etc)
2424
Map<String, String> generate() {
2525
final emitter = DartEmitter.scoped(useNullSafetySyntax: true);
26-
final formatter =
27-
DartFormatter(languageVersion: DartFormatter.latestLanguageVersion);
26+
final formatter = DartFormatter(
27+
languageVersion: DartFormatter.latestShortStyleLanguageVersion);
2828
return programMap.map((file, declMap) {
2929
final specs = declMap.decls.values.map((d) {
3030
return switch (d) {

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

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class Transformer {
4949
_transformFunction(node as TSFunctionDeclaration),
5050
TSSyntaxKind.EnumDeclaration =>
5151
_transformEnum(node as TSEnumDeclaration),
52+
TSSyntaxKind.TypeAliasDeclaration =>
53+
_transformTypeAlias(node as TSTypeAliasDeclaration),
5254
_ => throw Exception('Unsupported Declaration Kind: ${node.kind}')
5355
};
5456
// ignore: dead_code This line will not be dead in future decl additions
@@ -177,6 +179,30 @@ class Transformer {
177179
return declarations?.toDart.first;
178180
}
179181

182+
TypeAliasDeclaration _transformTypeAlias(TSTypeAliasDeclaration typealias) {
183+
final name = typealias.name.text;
184+
185+
final modifiers = typealias.modifiers?.toDart;
186+
final isExported = modifiers?.any((m) {
187+
return m.kind == TSSyntaxKind.ExportKeyword;
188+
}) ??
189+
false;
190+
191+
final typeParams = typealias.typeParameters?.toDart;
192+
193+
final type = typealias.type;
194+
195+
return TypeAliasDeclaration(
196+
name: name,
197+
// TODO: Can we find a way not to make the types be JS types
198+
// by default if possible. Leaving this for now,
199+
// so that using such typealiases in generics does not break
200+
type: _transformType(type),
201+
typeParameters:
202+
typeParams?.map(_transformTypeParamDeclaration).toList() ?? [],
203+
exported: isExported);
204+
}
205+
180206
FunctionDeclaration _transformFunction(TSFunctionDeclaration function) {
181207
final name = function.name.text;
182208

@@ -228,18 +254,20 @@ class Transformer {
228254

229255
GenericType _transformTypeParamDeclaration(
230256
TSTypeParameterDeclaration typeParam) {
257+
final constraint = typeParam.constraint == null
258+
? BuiltinType.anyType
259+
: _transformType(typeParam.constraint!, typeArg: true);
231260
return GenericType(
232261
name: typeParam.name.text,
233-
constraint: typeParam.constraint == null
234-
? BuiltinType.anyType
235-
: _transformType(typeParam.constraint!));
262+
constraint: getJSTypeAlternative(constraint));
236263
}
237264

238265
/// Parses the type
239266
///
240267
/// TODO(https://github.com/dart-lang/web/issues/384): Add support for literals (i.e individual booleans and `null`)
241268
/// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types
242-
Type _transformType(TSTypeNode type, {bool parameter = false}) {
269+
Type _transformType(TSTypeNode type,
270+
{bool parameter = false, bool typeArg = false}) {
243271
switch (type.kind) {
244272
case TSSyntaxKind.TypeReference:
245273
final refType = type as TSTypeReferenceNode;
@@ -255,7 +283,10 @@ class Transformer {
255283
// for this, and adding support for "supported declarations"
256284
// (also a better name for that)
257285
final supportedType = getSupportedType(
258-
name, (typeArguments ?? []).map(_transformType).toList());
286+
name,
287+
(typeArguments ?? [])
288+
.map((type) => _transformType(type, typeArg: true))
289+
.toList());
259290
if (supportedType != null) {
260291
return supportedType;
261292
}
@@ -280,8 +311,19 @@ class Transformer {
280311
final firstNode =
281312
declarationsMatching.whereType<NamedDeclaration>().first;
282313

314+
// For Typealiases, we can either return the type itself
315+
// or the JS Alternative (if its underlying type isn't a JS type)
316+
switch (firstNode) {
317+
case TypeAliasDeclaration(type: final t):
318+
case EnumDeclaration(baseType: final t):
319+
final jsType = getJSTypeAlternative(t);
320+
if (jsType != t && typeArg) return jsType;
321+
}
322+
283323
return firstNode.asReferredType(
284-
(typeArguments ?? []).map(_transformType).toList(),
324+
(typeArguments ?? [])
325+
.map((type) => _transformType(type, typeArg: true))
326+
.toList(),
285327
);
286328
// TODO: Union types are also anonymous by design
287329
// Unless we are making typedefs for them, we should
@@ -381,7 +423,8 @@ class Transformer {
381423
'The given type with kind ${type.kind} is not supported yet')
382424
};
383425

384-
return BuiltinType.primitiveType(primitiveType);
426+
return BuiltinType.primitiveType(primitiveType,
427+
shouldEmitJsType: typeArg ? true : null);
385428
}
386429
}
387430

@@ -445,6 +488,9 @@ class Transformer {
445488
break;
446489
case final EnumDeclaration _:
447490
break;
491+
case final TypeAliasDeclaration t:
492+
if (decl.type is! BuiltinType) filteredDeclarations.add(t.type);
493+
break;
448494
// TODO: We can make (DeclarationAssociatedType) and use that
449495
// rather than individual type names
450496
case final HomogenousEnumType hu:

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extension type const TSSyntaxKind._(num _) {
2424
static const TSSyntaxKind InterfaceDeclaration = TSSyntaxKind._(264);
2525
static const TSSyntaxKind FunctionDeclaration = TSSyntaxKind._(262);
2626
static const TSSyntaxKind ExportDeclaration = TSSyntaxKind._(278);
27+
static const TSSyntaxKind TypeAliasDeclaration = TSSyntaxKind._(265);
2728
static const TSSyntaxKind Parameter = TSSyntaxKind._(169);
2829
static const TSSyntaxKind EnumDeclaration = TSSyntaxKind._(266);
2930

@@ -178,6 +179,15 @@ extension type TSFunctionDeclaration._(JSObject _) implements TSDeclaration {
178179
external TSNodeArray<TSNode> get modifiers;
179180
}
180181

182+
@JS('TypeAliasDeclaration')
183+
extension type TSTypeAliasDeclaration._(JSObject _)
184+
implements TSDeclaration, TSStatement {
185+
external TSNodeArray<TSNode>? get modifiers;
186+
external TSNodeArray<TSTypeParameterDeclaration>? get typeParameters;
187+
external TSIdentifier get name;
188+
external TSTypeNode get type;
189+
}
190+
181191
@JS('ParameterDeclaration')
182192
extension type TSParameterDeclaration._(JSObject _) implements TSDeclaration {
183193
external TSNode get name;

web_generator/test/integration/interop_gen/enum_expected.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@ extension type const Permissions._(int _) {
104104
static const Permissions All = Permissions._(7);
105105
}
106106
@_i1.JS()
107-
external bool hasPermission(Permissions perm, Permissions flag);
107+
external bool hasPermission(
108+
Permissions perm,
109+
Permissions flag,
110+
);
108111
@_i1.JS()
109112
external Permissions get userPermissions;
110113
@_i1.JS()

web_generator/test/integration/interop_gen/functions_expected.dart

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,29 @@ external void logMessages(
1313
_i1.JSArray<_i1.JSString> messages4,
1414
]);
1515
@_i1.JS()
16-
external _i1.JSPromise<U> delay<U extends _i1.JSAny?>(num ms, [U? returnValue]);
16+
external _i1.JSPromise<U> delay<U extends _i1.JSAny?>(
17+
num ms, [
18+
U? returnValue,
19+
]);
1720
@_i1.JS()
1821
external _i1.JSArray<_i1.JSNumber> toArray(num a);
1922
@_i1.JS()
2023
external double square(num a);
2124
@_i1.JS()
2225
external double pow(num a);
2326
@_i1.JS('pow')
24-
external double pow$1(num a, num power);
27+
external double pow$1(
28+
num a,
29+
num power,
30+
);
2531
@_i1.JS('toArray')
2632
external _i1.JSArray<_i1.JSString> toArray$1(String a);
2733
@_i1.JS()
28-
external _i1.JSObject createUser(String name, [num? age, String? role]);
34+
external _i1.JSObject createUser(
35+
String name, [
36+
num? age,
37+
String? role,
38+
]);
2939
@_i1.JS()
3040
external T firstElement<T extends _i1.JSAny?>(_i1.JSArray<T> arr);
3141
@_i1.JS()
@@ -38,8 +48,7 @@ external T identity<T extends _i1.JSAny?>(T value);
3848
external void someFunction<A extends _i1.JSAny?>(_i1.JSArray<A> arr);
3949
@_i1.JS('someFunction')
4050
external B someFunction$1<A extends _i1.JSAny?, B extends _i1.JSAny?>(
41-
_i1.JSArray<A> arr,
42-
);
51+
_i1.JSArray<A> arr);
4352
@_i1.JS()
4453
external T logTuple<T extends _i1.JSArray<_i1.JSAny?>>(
4554
T args, [
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// ignore_for_file: constant_identifier_names, non_constant_identifier_names
2+
3+
// ignore_for_file: no_leading_underscores_for_library_prefixes
4+
import 'dart:js_interop' as _i1;
5+
6+
typedef Username = String;
7+
typedef Age = double;
8+
typedef IsActive = bool;
9+
typedef Tags = _i1.JSArray<_i1.JSString>;
10+
typedef List<T extends _i1.JSAny?> = _i1.JSArray<T>;
11+
typedef Box<T extends _i1.JSAny?> = _i1.JSArray<_i1.JSArray<T>>;
12+
typedef PromisedArray<U extends _i1.JSAny?, T extends _i1.JSArray<U>>
13+
= _i1.JSPromise<T>;
14+
typedef Shape2D = String;
15+
typedef PrismFromShape2D<S extends _i1.JSString> = _i1.JSArray<S>;
16+
typedef Logger = LoggerType;
17+
typedef Direction = AnonymousUnion;
18+
typedef Method = AnonymousUnion$1;
19+
@_i1.JS()
20+
external LoggerContainer<_i1.JSNumber> get loggerContainers;
21+
@_i1.JS()
22+
external Logger myLogger;
23+
@_i1.JS()
24+
external Method get requestMethod;
25+
@_i1.JS()
26+
external Username get username;
27+
@_i1.JS()
28+
external Age get age;
29+
@_i1.JS()
30+
external _i1.JSArray<Tags> get tagArray;
31+
@_i1.JS()
32+
external List<_i1.JSString> get users;
33+
@_i1.JS()
34+
external Box<_i1.JSNumber> get matrix;
35+
@_i1.JS()
36+
external PrismFromShape2D<_i1.JSString> makePrism(Shape2D shape);
37+
@_i1.JS('makePrism')
38+
external PrismFromShape2D<S> makePrism$1<S extends _i1.JSString>(S shape);
39+
@_i1.JS()
40+
external PromisedArray<_i1.JSString, _i1.JSArray<_i1.JSString>> fetchNames();
41+
@_i1.JS()
42+
external String isUserActive(IsActive status);
43+
extension type const LoggerType._(int _) {
44+
static const LoggerType Noop = LoggerType._(0);
45+
46+
static const LoggerType Stdout = LoggerType._(1);
47+
48+
static const LoggerType Stderr = LoggerType._(2);
49+
50+
static const LoggerType File = LoggerType._(3);
51+
52+
static const LoggerType Other = LoggerType._(4);
53+
}
54+
extension type const AnonymousUnion._(String _) {
55+
static const AnonymousUnion N = AnonymousUnion._('N');
56+
57+
static const AnonymousUnion S = AnonymousUnion._('S');
58+
59+
static const AnonymousUnion E = AnonymousUnion._('E');
60+
61+
static const AnonymousUnion W = AnonymousUnion._('W');
62+
}
63+
extension type const AnonymousUnion$1._(String _) {
64+
static const AnonymousUnion$1 GET = AnonymousUnion$1._('GET');
65+
66+
static const AnonymousUnion$1 POST = AnonymousUnion$1._('POST');
67+
68+
static const AnonymousUnion$1 PUT = AnonymousUnion$1._('PUT');
69+
70+
static const AnonymousUnion$1 DELETE = AnonymousUnion$1._('DELETE');
71+
72+
static const AnonymousUnion$1 PATCH = AnonymousUnion$1._('PATCH');
73+
74+
static const AnonymousUnion$1 OPTIONS = AnonymousUnion$1._('OPTIONS');
75+
}
76+
typedef LoggerContainer<N extends _i1.JSNumber> = _i1.JSArray<N>;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
declare enum LoggerType {
2+
Noop = 0,
3+
Stdout = 1,
4+
Stderr = 2,
5+
File = 3,
6+
Other = 4
7+
}
8+
export type Username = string;
9+
export type Age = number;
10+
export type IsActive = boolean;
11+
export type Tags = string[];
12+
export type List<T> = T[];
13+
export type Box<T> = Array<Array<T>>;
14+
export type PromisedArray<U, T extends Array<U>> = Promise<T>;
15+
export type Shape2D = string;
16+
export type PrismFromShape2D<S extends Shape2D> = Array<S>;
17+
export type Logger = LoggerType;
18+
export type Direction = "N" | "S" | "E" | "W";
19+
export type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
20+
type LoggerContainer<N extends number> = N[];
21+
export declare const loggerContainers: LoggerContainer<Logger>;
22+
export declare let myLogger: Logger;
23+
export declare const requestMethod: Method;
24+
export declare const username: Username;
25+
export declare const age: Age;
26+
export declare const tagArray: Tags[];
27+
export declare const users: List<Username>;
28+
export declare const matrix: Box<number>;
29+
export declare function makePrism(shape: Shape2D): PrismFromShape2D<Shape2D>;
30+
export declare function makePrism<S extends Shape2D>(shape: S): PrismFromShape2D<S>;
31+
export declare function fetchNames(): PromisedArray<string, string[]>;
32+
export declare function isUserActive(status: IsActive): string;

0 commit comments

Comments
 (0)