Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 88 additions & 16 deletions web_generator/lib/src/ast/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,41 @@ class UnionType extends DeclarationType {
}
}

class IntersectionType extends DeclarationType {
final List<Type> types;

@override
bool isNullable = false;

@override
String declarationName;

IntersectionType({required this.types, required String name})
: declarationName = name;

@override
ID get id => ID(type: 'type', name: types.map((t) => t.id.name).join('&'));

@override
Declaration get declaration =>
_IntersectionDeclaration(name: declarationName, types: types);

@override
Reference emit([TypeOptions? options]) {
return TypeReference((t) => t
..symbol = declarationName
..isNullable = (options?.nullable ?? false) || isNullable);
}

@override
int get hashCode => Object.hashAllUnordered(types);

@override
bool operator ==(Object other) {
return other is TupleType && other.types.every(types.contains);
}
}

class HomogenousEnumType<T extends LiteralType, D extends Declaration>
extends UnionType implements DeclarationType {
final List<T> _types;
Expand Down Expand Up @@ -491,25 +526,27 @@ class _ConstructorDeclaration extends CallableDeclaration
}
}

// TODO: Merge properties/methods of related types
class _UnionDeclaration extends NamedDeclaration
sealed class _UnionOrIntersectionDeclaration extends NamedDeclaration
implements ExportableDeclaration {
@override
bool get exported => true;

@override
ID get id => ID(type: 'union', name: name);

bool isNullable;
ID get id;

List<Type> types;

List<GenericType> typeParameters;

_UnionDeclaration(
@override
String name;

@override
String? dartName;

_UnionOrIntersectionDeclaration(
{required this.name,
this.types = const [],
this.isNullable = false,
List<GenericType>? typeParams})
: typeParameters = typeParams ?? [] {
if (typeParams == null) {
Expand All @@ -522,14 +559,10 @@ class _UnionDeclaration extends NamedDeclaration
}
}

@override
String? dartName;

@override
String name;

@override
Spec emit([covariant DeclarationOptions? options]) {
Spec _emit(
{covariant DeclarationOptions? options,
bool extendTypes = false,
bool isNullable = false}) {
options ??= DeclarationOptions();

final repType =
Expand All @@ -541,7 +574,13 @@ class _UnionDeclaration extends NamedDeclaration
..representationDeclaration = RepresentationDeclaration((r) => r
..name = '_'
..declaredRepresentationType = repType.emit(options?.toTypeOptions()))
..implements.addAll([repType.emit(options?.toTypeOptions())])
..implements.addAll([
if (extendTypes)
...types.map(
(t) => getJSTypeAlternative(t).emit(options?.toTypeOptions()))
else
repType.emit(options?.toTypeOptions())
])
..types
.addAll(typeParameters.map((t) => t.emit(options?.toTypeOptions())))
..methods.addAll(types.map((t) {
Expand Down Expand Up @@ -600,3 +639,36 @@ class _UnionDeclaration extends NamedDeclaration
})));
}
}

class _IntersectionDeclaration extends _UnionOrIntersectionDeclaration {
@override
bool get exported => true;

@override
ID get id => ID(type: 'intersection', name: name);

_IntersectionDeclaration({required super.name, super.types}) : super();

@override
Spec emit([covariant DeclarationOptions? options]) {
return super._emit(options: options, extendTypes: true);
}
}

class _UnionDeclaration extends _UnionOrIntersectionDeclaration {
@override
bool get exported => true;

@override
ID get id => ID(type: 'union', name: name);

bool isNullable;

_UnionDeclaration({required super.name, super.types, this.isNullable = false})
: super();

@override
Spec emit([covariant DeclarationOptions? options]) {
return super._emit(options: options, isNullable: isNullable);
}
}
47 changes: 44 additions & 3 deletions web_generator/lib/src/interop_gen/transform/transformer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,46 @@ class Transformer {
});
return unType..isNullable = shouldBeNullable;

case TSSyntaxKind.IntersectionType:
final intersectionType = type as TSIntersectionTypeNode;
final intersectionTypes = intersectionType.types.toDart;
final nonNullableIntersectionTypes = intersectionTypes
.where((t) =>
t.kind != TSSyntaxKind.UndefinedKeyword &&
!(t.kind == TSSyntaxKind.LiteralType &&
(t as TSLiteralTypeNode).literal.kind ==
TSSyntaxKind.NullKeyword))
.toList();
final shouldBeNullable =
nonNullableIntersectionTypes.length != intersectionTypes.length;

if (shouldBeNullable) return BuiltinType.$voidType;

if (nonNullableIntersectionTypes.singleOrNull
case final singleTypeNode?) {
return _transformType(singleTypeNode, isNullable: shouldBeNullable);
}

final types =
nonNullableIntersectionTypes.map<Type>(_transformType).toList();

final idMap = types.map((t) => t.id.name);
final expectedId = ID(type: 'type', name: idMap.join('&'));
if (typeMap.containsKey(expectedId.toString())) {
return typeMap[expectedId.toString()] as UnionType;
}

final intersectionHash = AnonymousHasher.hashUnion(idMap.toList());
final name = 'AnonymousIntersection_$intersectionHash';

final un = IntersectionType(types: types, name: name);

final unType = typeMap.putIfAbsent(expectedId.toString(), () {
namer.markUsed(name);
return un;
});

return unType..isNullable = shouldBeNullable;
case TSSyntaxKind.TupleType:
// tuple type is array
final tupleType = type as TSTupleTypeNode;
Expand Down Expand Up @@ -2138,12 +2178,13 @@ class Transformer {
for (final t in t.types.where((t) => t is! BuiltinType))
t.id.toString(): t
});
case final UnionType u:
case UnionType(types: final uTypes, declaration: final uDecl) ||
IntersectionType(types: final uTypes, declaration: final uDecl):
filteredDeclarations.addAll({
for (final t in u.types.where((t) => t is! BuiltinType))
for (final t in uTypes.where((t) => t is! BuiltinType))
t.id.toString(): t
});
filteredDeclarations.add(u.declaration);
filteredDeclarations.add(uDecl);
case final DeclarationType d:
filteredDeclarations.add(d.declaration);
break;
Expand Down
8 changes: 8 additions & 0 deletions web_generator/lib/src/js/typescript.types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ extension type const TSSyntaxKind._(num _) {

// types
static const TSSyntaxKind UnionType = TSSyntaxKind._(192);
static const TSSyntaxKind IntersectionType = TSSyntaxKind._(193);
static const TSSyntaxKind TypeReference = TSSyntaxKind._(183);
static const TSSyntaxKind ArrayType = TSSyntaxKind._(188);
static const TSSyntaxKind LiteralType = TSSyntaxKind._(201);
Expand Down Expand Up @@ -153,6 +154,13 @@ extension type TSUnionTypeNode._(JSObject _) implements TSTypeNode {
external TSNodeArray<TSTypeNode> get types;
}

@JS('IntersectionTypeNode')
extension type TSIntersectionTypeNode._(JSObject _) implements TSTypeNode {
@redeclare
TSSyntaxKind get kind => TSSyntaxKind.IntersectionType;
external TSNodeArray<TSTypeNode> get types;
}

@JS('TypeQueryNode')
extension type TSTypeQueryNode._(JSObject _) implements TSTypeNode {
@redeclare
Expand Down
94 changes: 94 additions & 0 deletions web_generator/test/integration/interop_gen/ts_typing_expected.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ external _i1.JSArray<AnonymousType_5780756> get shoppingCart;
external _AnonymousFunction_2181528 get createLogger;
@_i1.JS()
external _AnonymousFunction_1707607 get appLogger;
@_i1.JS()
external AnonymousIntersection_1467782 get myIntersection;
@_i1.JS()
external AnonymousIntersection_4895242 get mySecondIntersection;
@_i1.JS()
external AnonymousIntersection_1711585 get myTypeGymnastic;
extension type AnonymousType_9143117<T extends _i1.JSAny?>._(_i1.JSObject _)
implements _i1.JSObject {
external AnonymousType_9143117({
Expand Down Expand Up @@ -253,3 +259,91 @@ extension type _AnonymousFunction_1707607._(_i1.JSFunction _)
implements _i1.JSFunction {
external void call(String message);
}
extension type AnonymousIntersection_1467782._(_i1.JSAny _)
implements _i1.JSString, _i1.JSNumber {
String get asString => (_ as _i1.JSString).toDart;

double get asDouble => (_ as _i1.JSNumber).toDartDouble;
}
extension type AnonymousIntersection_4895242._(_i1.JSAny _)
implements _i1.JSString, AnonymousType_1178025, AnonymousType_8266437 {
String get asString => (_ as _i1.JSString).toDart;

AnonymousType_1178025 get asAnonymousType_1178025 =>
(_ as AnonymousType_1178025);

AnonymousType_8266437 get asAnonymousType_8266437 =>
(_ as AnonymousType_8266437);
}
extension type AnonymousType_1178025._(_i1.JSObject _) implements _i1.JSObject {
external AnonymousType_1178025({
String debugInfo,
_i1.JSSymbol tag,
});

external String debugInfo;

external _i1.JSSymbol tag;
}
extension type AnonymousType_8266437._(_i1.JSObject _) implements _i1.JSObject {
external AnonymousType_8266437();

@_i1.JS('toString')
external String toString$();
}
extension type AnonymousIntersection_1711585._(_i1.JSAny _)
implements AnonymousUnion_2392544, AnonymousUnion_5737239 {
AnonymousUnion_2392544 get asAnonymousUnion_2392544 =>
(_ as AnonymousUnion_2392544);

AnonymousUnion_5737239 get asAnonymousUnion_5737239 =>
(_ as AnonymousUnion_5737239);
}
extension type AnonymousUnion_2392544._(_i1.JSObject _)
implements _i1.JSObject {
AnonymousType_4207514 get asAnonymousType_4207514 =>
(_ as AnonymousType_4207514);

AnonymousType_1806035 get asAnonymousType_1806035 =>
(_ as AnonymousType_1806035);
}
extension type AnonymousType_4207514._(_i1.JSObject _) implements _i1.JSObject {
external AnonymousType_4207514({double a});

external double a;
}
extension type AnonymousType_1806035._(_i1.JSObject _) implements _i1.JSObject {
external AnonymousType_1806035({String b});

external String b;
}
extension type AnonymousUnion_5737239._(_i1.JSAny _) implements _i1.JSAny {
AnonymousType_1417026 get asAnonymousType_1417026 =>
(_ as AnonymousType_1417026);

AnonymousIntersection_4953753 get asAnonymousIntersection_4953753 =>
(_ as AnonymousIntersection_4953753);
}
extension type AnonymousType_1417026._(_i1.JSObject _) implements _i1.JSObject {
external AnonymousType_1417026({bool c});

external bool c;
}
extension type AnonymousIntersection_4953753._(_i1.JSObject _)
implements AnonymousType_1229657, AnonymousType_4825273 {
AnonymousType_1229657 get asAnonymousType_1229657 =>
(_ as AnonymousType_1229657);

AnonymousType_4825273 get asAnonymousType_4825273 =>
(_ as AnonymousType_4825273);
}
extension type AnonymousType_1229657._(_i1.JSObject _) implements _i1.JSObject {
external AnonymousType_1229657({_i1.JSBigInt d});

external _i1.JSBigInt d;
}
extension type AnonymousType_4825273._(_i1.JSObject _) implements _i1.JSObject {
external AnonymousType_4825273({_i1.JSSymbol e});

external _i1.JSSymbol e;
}
10 changes: 10 additions & 0 deletions web_generator/test/integration/interop_gen/ts_typing_input.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,13 @@ export declare const shoppingCart: {
}[];
export declare const createLogger: (prefix: string) => (message: string) => void;
export declare const appLogger: (message: string) => void;
export declare const myIntersection: string & number;
export declare const mySecondIntersection: string & {
debugInfo: string;
tag: symbol
} & {
toString(): string
};
export declare const myTypeGymnastic:
({ a: number } | { b: string }) &
({ c: boolean } | ({ d: bigint } & { e: symbol }));
Loading