Skip to content

Commit 1f80532

Browse files
[interop] Support typeof type declarations (#417)
* Interop Gen: Support `typeof` type declarations Fixes #383 * updated examples and code transformation * formatting * added documentation
1 parent affce52 commit 1f80532

File tree

9 files changed

+251
-37
lines changed

9 files changed

+251
-37
lines changed

web_generator/lib/src/ast/declarations.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ class VariableDeclaration extends FieldDeclaration
160160

161161
@override
162162
String? get dartName => null;
163+
164+
@override
165+
ReferredType<VariableDeclaration> asReferredType([List<Type>? typeArgs]) {
166+
return ReferredType<VariableDeclaration>.fromType(type, this,
167+
typeParams: typeArgs ?? []);
168+
}
163169
}
164170

165171
enum VariableModifier { let, $const, $var }
@@ -226,6 +232,14 @@ class FunctionDeclaration extends CallableDeclaration
226232
..requiredParameters.addAll(requiredParams)
227233
..optionalParameters.addAll(optionalParams));
228234
}
235+
236+
@override
237+
ReferredType<FunctionDeclaration> asReferredType([List<Type>? typeArgs]) {
238+
// TODO: We could do better here and make the function type typed
239+
return ReferredType<FunctionDeclaration>.fromType(
240+
BuiltinType.referred('Function', typeParams: typeArgs ?? [])!, this,
241+
typeParams: typeArgs ?? []);
242+
}
229243
}
230244

231245
class EnumDeclaration extends NamedDeclaration

web_generator/lib/src/ast/types.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'base.dart';
88
import 'builtin.dart';
99
import 'declarations.dart';
1010

11+
/// A type referring to a type in the TypeScript AST
1112
class ReferredType<T extends Declaration> extends Type {
1213
@override
1314
String name;
@@ -24,6 +25,9 @@ class ReferredType<T extends Declaration> extends Type {
2425
required this.declaration,
2526
this.typeParams = const []});
2627

28+
factory ReferredType.fromType(Type type, T declaration,
29+
{List<Type> typeParams}) = ReferredDeclarationType;
30+
2731
@override
2832
Reference emit([TypeOptions? options]) {
2933
// TODO: Support referred types imported from URL
@@ -34,6 +38,21 @@ class ReferredType<T extends Declaration> extends Type {
3438
}
3539
}
3640

41+
class ReferredDeclarationType<T extends Declaration> extends ReferredType<T> {
42+
Type type;
43+
44+
@override
45+
String get name => type.name ?? declaration.name;
46+
47+
ReferredDeclarationType(this.type, T declaration, {super.typeParams})
48+
: super(name: declaration.name, declaration: declaration);
49+
50+
@override
51+
Reference emit([covariant TypeOptions? options]) {
52+
return type.emit(options);
53+
}
54+
}
55+
3756
// TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`)
3857
class UnionType extends Type {
3958
final List<Type> types;

web_generator/lib/src/interop_gen/transform.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ void transformFile(ts.TSProgram program, String file,
115115
}).toJS as ts.TSNodeCallback);
116116

117117
// filter
118-
final resolvedMap = transformer.filter();
118+
final resolvedMap = transformer.filterAndReturn();
119119

120120
programDeclarationMap.addAll({file: resolvedMap});
121121
}

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

Lines changed: 99 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class Transformer {
5151
filterDeclSet = filterDeclSet.toList(),
5252
namer = UniqueNamer();
5353

54+
/// Transforms a TypeScript AST Node [TSNode] into a Dart representable [Node]
5455
void transform(TSNode node) {
5556
if (nodes.contains(node)) return;
5657

@@ -71,7 +72,6 @@ class Transformer {
7172
_transformClassOrInterface(node as TSObjectDeclaration),
7273
_ => throw Exception('Unsupported Declaration Kind: ${node.kind}')
7374
};
74-
// ignore: dead_code This line will not be dead in future decl additions
7575
nodeMap.add(decl);
7676
}
7777

@@ -696,9 +696,16 @@ class Transformer {
696696
constraint: getJSTypeAlternative(constraint));
697697
}
698698

699-
/// Parses the type
699+
/// Parses a TypeScript AST Type Node [TSTypeNode] into a [Type] Node
700+
/// used to represent a type
700701
///
701-
/// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types
702+
/// [parameter] represents whether the [TSTypeNode] is being passed in
703+
/// the context of a parameter, which is mainly used to differentiate between
704+
/// using [num] and [double] in the context of a [JSNumber]
705+
///
706+
/// [typeArg] represents whether the [TSTypeNode] is being passed in the
707+
/// context of a type argument, as Dart core types are not allowed in
708+
/// type arguments
702709
Type _transformType(TSTypeNode type,
703710
{bool parameter = false, bool typeArg = false}) {
704711
switch (type.kind) {
@@ -794,6 +801,15 @@ class Transformer {
794801
_ => throw UnimplementedError(
795802
'Unsupported Literal Kind ${literal.kind}')
796803
});
804+
case TSSyntaxKind.TypeQuery:
805+
final typeQuery = type as TSTypeQueryNode;
806+
807+
// TODO(nikeokoronkwo): Refactor this once #402 lands, https://github.com/dart-lang/web/pull/415
808+
final exprName = typeQuery.exprName;
809+
final typeArguments = typeQuery.typeArguments?.toDart;
810+
811+
return _getTypeFromDeclaration(exprName, typeArguments,
812+
typeArg: typeArg, isNotTypableDeclaration: true);
797813
case TSSyntaxKind.ArrayType:
798814
return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [
799815
getJSTypeAlternative(
@@ -838,9 +854,27 @@ class Transformer {
838854
}
839855
}
840856

857+
/// Get the type of a type node named [typeName] by referencing its
858+
/// declaration
859+
///
860+
/// This method uses the TypeScript type checker [ts.TSTypeChecker] to get the
861+
/// declaration associated with the [TSTypeNode] using its [typeName], and
862+
/// refer to that type either as a [ReferredType] if defined in the file, or
863+
/// not directly supported by `dart:js_interop`, or as a [BuiltinType] if
864+
/// supported by `dart:js_interop`
865+
///
866+
/// [typeArg] represents whether the [TSTypeNode] is being passed in the
867+
/// context of a type argument, as Dart core types are not allowed in
868+
/// type arguments
869+
///
870+
/// [isNotTypableDeclaration] represents whether the declaration to search for
871+
/// or refer to is not a typable declaration (i.e a declaration suitable for
872+
/// use in a `typeof` type node, such as a variable). This reduces checks on
873+
/// supported `dart:js_interop` types and related [EnumDeclaration]-like and
874+
/// [TypeDeclaration]-like checks
841875
Type _getTypeFromDeclaration(
842876
TSIdentifier typeName, List<TSTypeNode>? typeArguments,
843-
{bool typeArg = false}) {
877+
{bool typeArg = false, bool isNotTypableDeclaration = false}) {
844878
final name = typeName.text;
845879
var declarationsMatching = nodeMap.findByName(name);
846880

@@ -849,12 +883,14 @@ class Transformer {
849883
// TODO(https://github.com/dart-lang/web/issues/380): A better name
850884
// for this, and adding support for "supported declarations"
851885
// (also a better name for that)
852-
final supportedType = BuiltinType.referred(name,
853-
typeParams: (typeArguments ?? [])
854-
.map((t) => getJSTypeAlternative(_transformType(t)))
855-
.toList());
856-
if (supportedType case final resultType?) {
857-
return resultType;
886+
if (!isNotTypableDeclaration) {
887+
final supportedType = BuiltinType.referred(name,
888+
typeParams: (typeArguments ?? [])
889+
.map((t) => getJSTypeAlternative(_transformType(t)))
890+
.toList());
891+
if (supportedType case final resultType?) {
892+
return resultType;
893+
}
858894
}
859895

860896
final symbol = typeChecker.getSymbolAtLocation(typeName);
@@ -870,22 +906,24 @@ class Transformer {
870906
throw Exception('Found no declaration matching $name');
871907
}
872908

873-
// check if this is from dom
874-
final declarationSource = declaration.getSourceFile().fileName;
875-
if (p.basename(declarationSource) == 'lib.dom.d.ts' ||
876-
declarationSource.contains('dom')) {
877-
// dom declaration: supported by package:web
878-
// TODO(nikeokoronkwo): It is possible that we may get a type
879-
// that isn't in `package:web`
880-
return PackageWebType.parse(name,
881-
typeParams: (typeArguments ?? [])
882-
.map(_transformType)
883-
.map(getJSTypeAlternative)
884-
.toList());
885-
}
909+
if (!isNotTypableDeclaration) {
910+
// check if this is from dom
911+
final declarationSource = declaration.getSourceFile().fileName;
912+
if (p.basename(declarationSource) == 'lib.dom.d.ts' ||
913+
declarationSource.contains('dom')) {
914+
// dom declaration: supported by package:web
915+
// TODO(nikeokoronkwo): It is possible that we may get a type
916+
// that isn't in `package:web`
917+
return PackageWebType.parse(name,
918+
typeParams: typeArguments
919+
?.map((t) => getJSTypeAlternative(_transformType(t)))
920+
.toList() ??
921+
[]);
922+
}
886923

887-
if (declaration.kind == TSSyntaxKind.TypeParameter) {
888-
return GenericType(name: name);
924+
if (declaration.kind == TSSyntaxKind.TypeParameter) {
925+
return GenericType(name: name);
926+
}
889927
}
890928

891929
transform(declaration);
@@ -896,23 +934,45 @@ class Transformer {
896934
// TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
897935
final firstNode = declarationsMatching.whereType<NamedDeclaration>().first;
898936

899-
// For Typealiases, we can either return the type itself
900-
// or the JS Alternative (if its underlying type isn't a JS type)
901-
switch (firstNode) {
902-
case TypeAliasDeclaration(type: final t):
903-
case EnumDeclaration(baseType: final t):
904-
final jsType = getJSTypeAlternative(t);
905-
if (jsType != t && typeArg) return jsType;
937+
if (!isNotTypableDeclaration) {
938+
// For Typealiases, we can either return the type itself
939+
// or the JS Alternative (if its underlying type isn't a JS type)
940+
switch (firstNode) {
941+
case TypeAliasDeclaration(type: final t):
942+
case EnumDeclaration(baseType: final t):
943+
final jsType = getJSTypeAlternative(t);
944+
if (jsType != t && typeArg) return jsType;
945+
}
906946
}
907947

908-
return firstNode.asReferredType(
948+
final asReferredType = firstNode.asReferredType(
909949
(typeArguments ?? [])
910950
.map((type) => _transformType(type, typeArg: true))
911951
.toList(),
912952
);
953+
954+
if (asReferredType case ReferredDeclarationType(type: final type)
955+
when type is BuiltinType) {
956+
final jsType = getJSTypeAlternative(type);
957+
if (jsType != type && typeArg) asReferredType.type = jsType;
958+
}
959+
960+
return asReferredType;
913961
}
914962

915-
NodeMap filter() {
963+
/// Filters out the declarations generated from the [transform] function and
964+
/// returns the declarations needed based on:
965+
///
966+
/// - Whether they are exported (contains the `export` keyword, or is in an
967+
/// export declaration captured by [exportSet])
968+
/// - Whether they are denoted to be included in configuration
969+
/// ([filterDeclSet])
970+
///
971+
/// The function also goes through declaration dependencies and filters those
972+
/// in too
973+
///
974+
/// Returns a [NodeMap] containing a map of the declared nodes and IDs.
975+
NodeMap filterAndReturn() {
916976
final filteredDeclarations = NodeMap();
917977

918978
// filter out for export declarations
@@ -1026,8 +1086,11 @@ class Transformer {
10261086
t.id.toString(): t
10271087
});
10281088
break;
1029-
case final BuiltinType _:
1030-
// primitive types are generated by default
1089+
case BuiltinType(typeParams: final typeParams) when typeParams.isNotEmpty:
1090+
filteredDeclarations.addAll({
1091+
for (final t in typeParams.where((t) => t is! BuiltinType))
1092+
t.id.toString(): t
1093+
});
10311094
break;
10321095
case final ReferredType r:
10331096
filteredDeclarations.add(r.declaration);

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ extension type const TSSyntaxKind._(num _) {
7878
static const TSSyntaxKind ArrayType = TSSyntaxKind._(188);
7979
static const TSSyntaxKind LiteralType = TSSyntaxKind._(201);
8080
static const TSSyntaxKind ThisType = TSSyntaxKind._(197);
81+
static const TSSyntaxKind TypeQuery = TSSyntaxKind._(186);
8182

8283
/// Other
8384
static const TSSyntaxKind Identifier = TSSyntaxKind._(80);
@@ -127,6 +128,21 @@ extension type TSUnionTypeNode._(JSObject _) implements TSTypeNode {
127128
external TSNodeArray<TSTypeNode> get types;
128129
}
129130

131+
// TODO(nikeokoronkwo): Implements TSNodeWithTypeArguments
132+
// once #402 and #409 are closed
133+
@JS('TypeQueryNode')
134+
extension type TSTypeQueryNode._(JSObject _) implements TSTypeNode {
135+
@redeclare
136+
TSSyntaxKind get kind => TSSyntaxKind.TypeQuery;
137+
138+
// TODO(nikeokoronkwo): Change to EntityName to support
139+
// qualified names, https://github.com/dart-lang/web/issues/416
140+
external TSIdentifier get exprName;
141+
external TSNodeArray<TSTypeNode>? get typeArguments;
142+
}
143+
144+
// TODO(nikeokoronkwo): Implements TSNodeWithTypeArguments
145+
// once #402 and #409 are closed
130146
@JS('TypeReferenceNode')
131147
extension type TSTypeReferenceNode._(JSObject _) implements TSTypeNode {
132148
@redeclare
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
@_i1.JS()
7+
external String get myString;
8+
@_i1.JS()
9+
external _i1.JSArray<_i1.JSNumber> get myNumberArray;
10+
@_i1.JS()
11+
external String get myCloneString;
12+
@_i1.JS()
13+
external _i1.JSArray<_i1.JSArray<_i1.JSNumber>> get myCloneNumberArray;
14+
extension type const MyEnum._(int _) {
15+
static const MyEnum A = MyEnum._(0);
16+
17+
static const MyEnum B = MyEnum._(1);
18+
19+
static const MyEnum C = MyEnum._(2);
20+
21+
static const MyEnum D = MyEnum._(4);
22+
}
23+
@_i1.JS()
24+
external String myFunction(String param);
25+
@_i1.JS()
26+
external String myEnclosingFunction(_i1.JSFunction func);
27+
@_i1.JS()
28+
external _i1.JSFunction copyOfmyEnclosingFunction;
29+
@_i1.JS()
30+
external MyEnum get myEnumValue;
31+
@_i1.JS()
32+
external MyEnum get myEnumValue2;
33+
@_i1.JS()
34+
external _i1.JSFunction myFunctionAlias;
35+
@_i1.JS()
36+
external _i1.JSFunction myFunctionAlias2;
37+
@_i1.JS()
38+
external _i1.JSFunction get myEnclosingFunctionAlias;
39+
@_i1.JS()
40+
external ComposedType get myComposedType;
41+
@_i1.JS()
42+
external ComposedType<_i1.JSString> get myComposedMyString;
43+
extension type ComposedType<T extends _i1.JSAny?>._(_i1.JSObject _)
44+
implements _i1.JSObject {
45+
external T enclosed;
46+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export declare const myString: string;
2+
export declare const myNumberArray: number[];
3+
export declare const myCloneString: typeof myString;
4+
export declare const myCloneNumberArray: typeof myNumberArray[];
5+
export declare enum MyEnum {
6+
A = 0,
7+
B = 1,
8+
C = 2,
9+
D = 4
10+
}
11+
interface ComposedType<T = any> {
12+
enclosed: T;
13+
}
14+
export declare let copyOfmyEnclosingFunction: typeof myEnclosingFunction;
15+
export declare const myEnumValue: MyEnum;
16+
export declare const myEnumValue2: typeof MyEnum;
17+
export declare function myFunction(param: string): string;
18+
export declare let myFunctionAlias: typeof myFunction;
19+
export declare let myFunctionAlias2: typeof myFunctionAlias;
20+
/** @todo [@nikeokoronkwo] support var declarations as well as var statements */
21+
// export declare let myPreClone: typeof myComposedType;
22+
export declare function myEnclosingFunction(func: typeof myFunction): string;
23+
export declare const myEnclosingFunctionAlias: typeof myEnclosingFunction;
24+
export declare const myComposedType: ComposedType;
25+
export declare const myComposedMyString: ComposedType<typeof myString>;

0 commit comments

Comments
 (0)