Skip to content
Merged
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
127 changes: 111 additions & 16 deletions web_generator/lib/src/ast/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,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 @@ -557,25 +592,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 @@ -588,26 +625,42 @@ 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 =
getLowestCommonAncestorOfTypes(types, isNullable: isNullable);

final extendees = <Type>[];
if (extendTypes) {
// check if any types are primitive
// TODO: We can be much smarter about this, but this works best so far
if (types.any((t) {
final jsAltType = getJSTypeAlternative(t);
return jsAltType is BuiltinType &&
_nonObjectRepTypeTypes.contains(jsAltType.name);
}) ||
(repType is BuiltinType && repType.name == 'JSAny')) {
extendees.add(
BuiltinType.primitiveType(PrimitiveType.any, isNullable: false));
} else {
extendees.addAll(types.map(getJSTypeAlternative));
}
} else {
extendees.add(repType);
}

return ExtensionType((e) => e
..name = name
..primaryConstructorName = '_'
..representationDeclaration = RepresentationDeclaration((r) => r
..name = '_'
..declaredRepresentationType = repType.emit(options?.toTypeOptions()))
..implements.addAll([repType.emit(options?.toTypeOptions())])
..implements
.addAll(extendees.map((e) => e.emit(options?.toTypeOptions())))
..types
.addAll(typeParameters.map((t) => t.emit(options?.toTypeOptions())))
..methods.addAll(types.map((t) {
Expand Down Expand Up @@ -719,3 +772,45 @@ class _EnumObjDeclaration extends NamedDeclaration
@override
ID get id => ID(type: 'enum-rep', name: name);
}

List<String> _nonObjectRepTypeTypes = [
'JSAny',
'JSString',
'JSBoolean',
'JSNumber',
'JSSymbol',
'JSBigInt'
];

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);
}
}
57 changes: 52 additions & 5 deletions web_generator/lib/src/interop_gen/transform/transformer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1268,7 +1268,8 @@ class Transformer {
nonNullableUnionTypes.length != unionTypes.length;

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

final types = nonNullableUnionTypes.map<Type>(_transformType).toList();
Expand Down Expand Up @@ -1307,7 +1308,8 @@ class Transformer {
final expectedId = ID(type: 'type', name: idMap.join('|'));

if (typeMap.containsKey(expectedId.toString())) {
return typeMap[expectedId.toString()] as UnionType;
return (typeMap[expectedId.toString()] as UnionType)
..isNullable = (isNullable ?? false);
}

final name =
Expand All @@ -1323,6 +1325,50 @@ 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.primitiveType(PrimitiveType.never,
isNullable: isNullable);
}

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

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 IntersectionType)
..isNullable = (isNullable ?? false);
}

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 = isNullable ?? shouldBeNullable;
case TSSyntaxKind.TupleType:
// tuple type is array
final tupleType = type as TSTupleTypeNode;
Expand Down Expand Up @@ -2332,12 +2378,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 @@ -87,6 +87,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 @@ -158,6 +159,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
Loading