Skip to content

Commit 43621fd

Browse files
[interop] Add Support for keyof and readonly type operators (#452)
* [interop] Add Support for `keyof` and `readonly` type operators * wip: tuples * completed tuple readonly support * renamed naming for `keyof` and `typeof` (enum)
1 parent f6389df commit 43621fd

File tree

7 files changed

+330
-11
lines changed

7 files changed

+330
-11
lines changed

web_generator/lib/src/ast/helpers.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ class TupleDeclaration extends NamedDeclaration
267267
bool get exported => true;
268268

269269
@override
270-
ID get id => ID(type: 'tuple', name: name);
270+
ID get id => ID(type: 'tuple', name: name, index: readonly ? 1 : 0);
271271

272272
final int count;
273273

@@ -294,7 +294,11 @@ class TupleDeclaration extends NamedDeclaration
294294
[List<Type>? typeArgs, bool isNullable = false, String? url]) {
295295
assert(typeArgs?.length == count,
296296
'Type arguments must equal the number of tuples supported');
297-
return TupleType(types: typeArgs ?? [], tupleDeclUrl: url);
297+
return TupleType(
298+
types: typeArgs ?? [],
299+
tupleDeclUrl: url,
300+
readonly: readonly,
301+
decl: this);
298302
}
299303

300304
@override

web_generator/lib/src/ast/types.dart

Lines changed: 125 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,27 @@ class TupleType extends ReferredType<TupleDeclaration> {
7979
@override
8080
List<Type> get typeParams => types;
8181

82+
bool readonly;
83+
8284
TupleType(
83-
{required this.types, super.isNullable, required String? tupleDeclUrl})
85+
{required this.types,
86+
super.isNullable,
87+
required String? tupleDeclUrl,
88+
this.readonly = false,
89+
TupleDeclaration? decl})
8490
: super(
85-
declaration: TupleDeclaration(count: types.length),
86-
name: 'JSTuple${types.length}',
91+
declaration: decl ??
92+
TupleDeclaration(count: types.length, readonly: readonly),
93+
name: readonly
94+
? 'JSReadonlyTuple${types.length}'
95+
: 'JSTuple${types.length}',
8796
url: tupleDeclUrl);
8897

8998
@override
90-
ID get id => ID(type: 'type', name: types.map((t) => t.id.name).join(','));
99+
ID get id => ID(
100+
type: 'type',
101+
name: types.map((t) => t.id.name).join(','),
102+
index: readonly ? 1 : 0);
91103

92104
@override
93105
int get hashCode => Object.hashAllUnordered(types);
@@ -283,19 +295,34 @@ class ObjectLiteralType extends DeclarationType<TypeDeclaration> {
283295
@override
284296
final ID id;
285297

298+
bool readonly = false;
299+
300+
String? _dartName;
301+
302+
@override
303+
String? get dartName => _dartName;
304+
305+
@override
306+
set dartName(String? value) {
307+
_dartName = value;
308+
}
309+
286310
ObjectLiteralType(
287311
{required String name,
288312
required this.id,
289313
this.properties = const [],
290314
this.methods = const [],
291315
this.constructors = const [],
292316
this.operators = const [],
293-
this.isNullable = false})
294-
: declarationName = name;
317+
this.isNullable = false,
318+
String? declarationDartName})
319+
: declarationName = name,
320+
_dartName = declarationDartName;
295321

296322
@override
297323
TypeDeclaration get declaration => InterfaceDeclaration(
298324
name: declarationName,
325+
dartName: dartName,
299326
exported: true,
300327
id: ID(type: 'interface', name: id.name),
301328
objectLiteralConstructor: true,
@@ -317,6 +344,45 @@ class ObjectLiteralType extends DeclarationType<TypeDeclaration> {
317344
}
318345
}
319346

347+
/// An object representation of a typescript enum, usually used
348+
/// either from a `typeof` expression, or a direct assignment.
349+
class EnumObjectType extends DeclarationType {
350+
final EnumDeclaration enumeration;
351+
352+
String? _dartName;
353+
354+
@override
355+
String? get dartName => _dartName;
356+
357+
@override
358+
set dartName(String? value) {
359+
_dartName = value;
360+
}
361+
362+
@override
363+
bool isNullable;
364+
365+
EnumObjectType(this.enumeration, {String? dartName, this.isNullable = false})
366+
: _dartName = dartName ?? enumeration.dartName;
367+
368+
@override
369+
ID get id => ID(type: 'type', name: 'TypeOf_${enumeration.name}');
370+
371+
@override
372+
Declaration get declaration => _EnumObjDeclaration(
373+
name: declarationName, dartName: dartName, reference: enumeration);
374+
375+
@override
376+
String get declarationName => '${enumeration.name}_EnumType';
377+
378+
@override
379+
Reference emit([covariant TypeOptions? options]) {
380+
return TypeReference((t) => t
381+
..symbol = declarationName
382+
..isNullable = options?.nullable ?? isNullable);
383+
}
384+
}
385+
320386
sealed class ClosureType extends DeclarationType {
321387
final List<ParameterDeclaration> parameters;
322388
final Type returnType;
@@ -600,3 +666,56 @@ class _UnionDeclaration extends NamedDeclaration
600666
})));
601667
}
602668
}
669+
670+
class _EnumObjDeclaration extends NamedDeclaration
671+
implements ExportableDeclaration {
672+
@override
673+
bool get exported => true;
674+
675+
@override
676+
String? dartName;
677+
678+
@override
679+
String name;
680+
681+
EnumDeclaration reference;
682+
683+
_EnumObjDeclaration(
684+
{required this.name, this.dartName, required this.reference});
685+
686+
@override
687+
Spec emit([covariant DeclarationOptions? options]) {
688+
final repType = BuiltinType.primitiveType(PrimitiveType.object);
689+
return ExtensionType((e) => e
690+
..name = dartName ?? name
691+
..annotations.addAll([
692+
if (dartName != null && dartName != name) generateJSAnnotation(name)
693+
])
694+
..primaryConstructorName = '_'
695+
..representationDeclaration = RepresentationDeclaration((r) => r
696+
..declaredRepresentationType = repType.emit(options?.toTypeOptions())
697+
..name = '_')
698+
..implements.add(repType.emit(options?.toTypeOptions()))
699+
..fields.addAll(reference.members.map((mem) => mem.emit()))
700+
..methods.addAll(reference.members.map((mem) {
701+
return mem.value == null
702+
? null
703+
: Method((m) => m
704+
..name =
705+
mem.value is int ? '\$${mem.value}' : mem.value.toString()
706+
..annotations.addAll([
707+
if (mem.value is int)
708+
refer('JS', 'dart:js_interop')
709+
.call([literalString(mem.value.toString())])
710+
])
711+
..type = MethodType.getter
712+
..returns = refer('String')
713+
..lambda = true
714+
..static = true
715+
..body = literalString(mem.name).code);
716+
}).nonNulls));
717+
}
718+
719+
@override
720+
ID get id => ID(type: 'enum-rep', name: name);
721+
}

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

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:collection';
66
import 'dart:js_interop';
7+
import 'package:collection/collection.dart';
78
import 'package:path/path.dart' as p;
89
import '../../ast/base.dart';
910
import '../../ast/builtin.dart';
@@ -1338,10 +1339,104 @@ class Transformer {
13381339
final exprName = typeQuery.exprName;
13391340
final typeArguments = typeQuery.typeArguments?.toDart;
13401341

1341-
return _getTypeFromDeclaration(exprName, typeArguments,
1342+
final getTypeFromDeclaration = _getTypeFromDeclaration(
1343+
exprName, typeArguments,
13421344
typeArg: typeArg,
13431345
isNotTypableDeclaration: true,
13441346
isNullable: isNullable ?? false);
1347+
1348+
switch (getTypeFromDeclaration) {
1349+
case ReferredType(
1350+
declaration: final referredDecl,
1351+
)
1352+
when referredDecl is EnumDeclaration:
1353+
// check for type in type map
1354+
final enumName = 'TypeOf_${referredDecl.name}';
1355+
final enumID = ID(type: 'type', name: enumName);
1356+
1357+
// enum is actually an object
1358+
return typeMap.putIfAbsent(enumID.toString(), () {
1359+
return EnumObjectType(referredDecl,
1360+
isNullable: isNullable ?? false);
1361+
});
1362+
default:
1363+
return getTypeFromDeclaration;
1364+
}
1365+
case TSSyntaxKind.TypeOperator
1366+
when (type as TSTypeOperatorNode).operator ==
1367+
TSSyntaxKind.ReadonlyKeyword:
1368+
final transformedType = _transformType(type.type,
1369+
parameter: parameter, typeArg: typeArg, isNullable: isNullable);
1370+
switch (transformedType) {
1371+
// turn tuple to readonly tuple
1372+
case final TupleType tuple:
1373+
// make readonly
1374+
final (tupleUrl, tupleDeclaration) = programMap.getCommonType(
1375+
'JSReadonlyTuple${tuple.types.length}',
1376+
ifAbsent: (
1377+
'_tuples.dart',
1378+
TupleDeclaration(count: tuple.types.length, readonly: true)
1379+
))!;
1380+
1381+
return tupleDeclaration.asReferredType(
1382+
tuple.types, isNullable ?? false, tupleUrl);
1383+
// TODO: mapped types
1384+
// by default just return
1385+
default:
1386+
return transformedType;
1387+
}
1388+
case TSSyntaxKind.TypeOperator
1389+
when (type as TSTypeOperatorNode).operator ==
1390+
TSSyntaxKind.KeyOfKeyword:
1391+
(List<String>, Type?) extractKeysOrReturnType(Type targetType) {
1392+
switch (targetType) {
1393+
case ObjectLiteralType(
1394+
properties: final objectProps,
1395+
):
1396+
return (objectProps.map((o) => o.name).toList(), null);
1397+
case EnumObjectType(enumeration: final enumeration):
1398+
return (enumeration.members.map((e) => e.name).toList(), null);
1399+
case ReferredType(declaration: final referredDecl)
1400+
when referredDecl is InterfaceDeclaration:
1401+
return (
1402+
referredDecl.properties.map((o) => o.name).toList(),
1403+
null
1404+
);
1405+
case ReferredDeclarationType(type: final referredToType):
1406+
return extractKeysOrReturnType(referredToType);
1407+
default:
1408+
return (
1409+
[],
1410+
BuiltinType.primitiveType(PrimitiveType.string,
1411+
isNullable: isNullable)
1412+
);
1413+
}
1414+
}
1415+
1416+
final transformedType = _transformType(type.type,
1417+
parameter: parameter, typeArg: typeArg, isNullable: isNullable);
1418+
1419+
// keyof
1420+
final (keys, returnTypeOrNull) =
1421+
extractKeysOrReturnType(transformedType);
1422+
1423+
if (returnTypeOrNull != null) return returnTypeOrNull;
1424+
1425+
final typeName = transformedType is NamedType
1426+
? (transformedType.dartName ?? transformedType.name)
1427+
: transformedType.id.name;
1428+
return HomogenousEnumType(
1429+
types: keys
1430+
.map((k) => LiteralType(kind: LiteralKind.string, value: k))
1431+
.toList(),
1432+
name: 'KeyOf_$typeName');
1433+
case TSSyntaxKind.TypeOperator
1434+
when (type as TSTypeOperatorNode).operator ==
1435+
TSSyntaxKind.UniqueKeyword:
1436+
// Dart does not support unique symbols
1437+
1438+
return _transformType(type.type,
1439+
parameter: parameter, typeArg: typeArg, isNullable: isNullable);
13451440
case TSSyntaxKind.ArrayType:
13461441
return BuiltinType.primitiveType(PrimitiveType.array,
13471442
typeParams: [
@@ -2065,6 +2160,10 @@ class Transformer {
20652160
void updateFilteredDeclsForDecl(Node? decl, NodeMap filteredDeclarations) {
20662161
switch (decl) {
20672162
case final VariableDeclaration v:
2163+
print((
2164+
v.name,
2165+
v.type is TupleType ? (v.type as TupleType).name : null
2166+
));
20682167
filteredDeclarations.add(v.type);
20692168
break;
20702169
case final CallableDeclaration f:

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,15 @@ extension type const TSSyntaxKind._(num _) {
6161
static const TSSyntaxKind WithKeyword = TSSyntaxKind._(118);
6262
static const TSSyntaxKind AssertKeyword = TSSyntaxKind._(132);
6363
static const TSSyntaxKind AbstractKeyword = TSSyntaxKind._(128);
64+
static const TSSyntaxKind KeyOfKeyword = TSSyntaxKind._(143);
65+
static const TSSyntaxKind UniqueKeyword = TSSyntaxKind._(158);
66+
static const TSSyntaxKind ReadonlyKeyword = TSSyntaxKind._(148);
6467

6568
// keywords for scope
6669
static const TSSyntaxKind PrivateKeyword = TSSyntaxKind._(123);
6770
static const TSSyntaxKind ProtectedKeyword = TSSyntaxKind._(124);
6871
static const TSSyntaxKind PublicKeyword = TSSyntaxKind._(125);
6972
static const TSSyntaxKind StaticKeyword = TSSyntaxKind._(126);
70-
static const TSSyntaxKind ReadonlyKeyword = TSSyntaxKind._(148);
7173

7274
// types that are keywords
7375
static const TSSyntaxKind StringKeyword = TSSyntaxKind._(154);
@@ -96,6 +98,7 @@ extension type const TSSyntaxKind._(num _) {
9698
static const TSSyntaxKind TypeLiteral = TSSyntaxKind._(187);
9799
static const TSSyntaxKind FunctionType = TSSyntaxKind._(184);
98100
static const TSSyntaxKind ConstructorType = TSSyntaxKind._(185);
101+
static const TSSyntaxKind TypeOperator = TSSyntaxKind._(198);
99102

100103
// Other
101104
static const TSSyntaxKind Identifier = TSSyntaxKind._(80);
@@ -210,6 +213,12 @@ extension type TSTypeLiteralNode._(JSObject _)
210213
external TSNodeArray<TSTypeElement> get members;
211214
}
212215

216+
@JS('TypeOperatorNode')
217+
extension type TSTypeOperatorNode._(JSObject _) implements TSTypeNode {
218+
external TSSyntaxKind operator;
219+
external TSTypeNode type;
220+
}
221+
213222
@JS('FunctionOrConstructorTypeNodeBase')
214223
extension type TSFunctionOrConstructorTypeNodeBase._(JSObject _)
215224
implements TSTypeNode, TSSignatureDeclarationBase {

web_generator/test/integration/interop_gen/_tuples.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,30 @@ extension type JSTuple2<A extends _i1.JSAny?, B extends _i1.JSAny?>._(
1414

1515
set $2(B newValue) => _[1] = newValue;
1616
}
17+
extension type JSTuple3<A extends _i1.JSAny?, B extends _i1.JSAny?,
18+
C extends _i1.JSAny?>._(_i1.JSArray<_i1.JSAny?> _)
19+
implements _i1.JSArray<_i1.JSAny?> {
20+
A get $1 => (_[0] as A);
21+
22+
B get $2 => (_[1] as B);
23+
24+
C get $3 => (_[2] as C);
25+
26+
set $1(A newValue) => _[0] = newValue;
27+
28+
set $2(B newValue) => _[1] = newValue;
29+
30+
set $3(C newValue) => _[2] = newValue;
31+
}
32+
extension type JSReadonlyTuple3<A extends _i1.JSAny?, B extends _i1.JSAny?,
33+
C extends _i1.JSAny?>._(_i1.JSArray<_i1.JSAny?> _)
34+
implements _i1.JSArray<_i1.JSAny?> {
35+
A get $1 => (_[0] as A);
36+
37+
B get $2 => (_[1] as B);
38+
39+
C get $3 => (_[2] as C);
40+
}
1741
extension type JSTuple4<A extends _i1.JSAny?, B extends _i1.JSAny?,
1842
C extends _i1.JSAny?, D extends _i1.JSAny?>._(_i1.JSArray<_i1.JSAny?> _)
1943
implements _i1.JSArray<_i1.JSAny?> {

0 commit comments

Comments
 (0)