Skip to content

[interop] Add Support for Anonymous Declarations #434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Aug 15, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1c76d12
wip: union types
nikeokoronkwo Aug 1, 2025
d79c2cf
wip: completed anonymous unions
nikeokoronkwo Aug 2, 2025
38b3aab
implemented JS Tuple
nikeokoronkwo Aug 2, 2025
cb90943
completed nullability support for `undefined` and `null`
nikeokoronkwo Aug 2, 2025
37e0d1a
Merge branch 'main' into interop/anonymous
nikeokoronkwo Aug 6, 2025
0b943e7
wip: object decl
nikeokoronkwo Aug 6, 2025
e7535a4
implemented anonymous objects
nikeokoronkwo Aug 6, 2025
c49ce6c
added support for closures and constructors
nikeokoronkwo Aug 7, 2025
f1a9e1c
wip: type hierarchy
nikeokoronkwo Aug 7, 2025
7f0a2e0
implemented sub type deduction
nikeokoronkwo Aug 7, 2025
f57165c
added type generics to union types
nikeokoronkwo Aug 7, 2025
49aa015
changed `.reduce` to `.fold`
nikeokoronkwo Aug 7, 2025
574250f
resolved newline and license headers
nikeokoronkwo Aug 8, 2025
283ab5d
isNullable updates and renamed `DeclarationAssociatedType`
nikeokoronkwo Aug 8, 2025
4a9bca6
updated algorithm to use LCA via Topological Ordering
nikeokoronkwo Aug 11, 2025
78a535f
resolved some more comments
nikeokoronkwo Aug 12, 2025
5421359
rm hasher_test
nikeokoronkwo Aug 12, 2025
f92ffc9
resolved some more comments
nikeokoronkwo Aug 12, 2025
9944b7f
refactored tuple generation (common types) and more
nikeokoronkwo Aug 12, 2025
c665449
added LCA test
nikeokoronkwo Aug 14, 2025
3dae04e
removed stray prints
nikeokoronkwo Aug 14, 2025
6632bc0
Merge branch 'main' into interop/anonymous
nikeokoronkwo Aug 14, 2025
5326ae0
added doc support and resolved merge
nikeokoronkwo Aug 14, 2025
552a461
updated documentation formatting using formatting.dart
nikeokoronkwo Aug 15, 2025
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
6 changes: 4 additions & 2 deletions web_generator/lib/src/ast/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ class ASTOptions {
}

sealed class Node {
String? get name;
abstract final ID id;
String? get dartName;

Expand All @@ -54,7 +53,6 @@ sealed class Node {
}

abstract class Declaration extends Node {
@override
String get name;

@override
Expand Down Expand Up @@ -147,3 +145,7 @@ class ParameterDeclaration {
..type = type.emit(TypeOptions(nullable: optional)));
}
}

abstract class NamedType extends Type {
String get name;
}
4 changes: 2 additions & 2 deletions web_generator/lib/src/ast/builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import 'base.dart';

/// A built in type supported by `dart:js_interop` or by this library
/// (with generated declarations)
class BuiltinType extends Type {
class BuiltinType extends NamedType {
@override
final String name;

Expand Down Expand Up @@ -129,7 +129,7 @@ class BuiltinType extends Type {
}
}

class PackageWebType extends Type {
class PackageWebType extends NamedType {
@override
final String name;

Expand Down
53 changes: 32 additions & 21 deletions web_generator/lib/src/ast/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,23 +97,23 @@ Set<String> getMemberHierarchy(TypeDeclaration type,
return members;
}

Type getRepresentationType(TypeDeclaration cl) {
if (cl case ClassDeclaration(extendedType: final extendee?)) {
Type getRepresentationType(TypeDeclaration td) {
if (td case ClassDeclaration(extendedType: final extendee?)) {
return switch (extendee) {
ReferredType(declaration: final d) when d is ClassDeclaration =>
ReferredType(declaration: final d) when d is TypeDeclaration =>
getRepresentationType(d),
final BuiltinType b => b,
_ => BuiltinType.primitiveType(PrimitiveType.object, isNullable: false)
};
} else if (cl case InterfaceDeclaration(extendedTypes: [final extendee])) {
} else if (td case InterfaceDeclaration(extendedTypes: [final extendee])) {
return switch (extendee) {
ReferredType(declaration: final d) when d is TypeDeclaration =>
getRepresentationType(d),
final BuiltinType b => b,
_ => BuiltinType.primitiveType(PrimitiveType.object, isNullable: false)
};
} else {
final primitiveType = switch (cl.name) {
final primitiveType = switch (td.name) {
'Array' => PrimitiveType.array,
_ => PrimitiveType.object
};
Expand Down Expand Up @@ -152,7 +152,6 @@ List<GenericType> getGenericTypes(Type t) {
break;
case ReferredType(typeParams: final referredTypeParams):
case UnionType(types: final referredTypeParams):
case TupleType(types: final referredTypeParams):
for (final referredTypeParam in referredTypeParams) {
types.addAll(getGenericTypes(referredTypeParam)
.map((t) => (t.name, t.constraint)));
Expand All @@ -179,7 +178,7 @@ List<GenericType> getGenericTypes(Type t) {
for (final type in typeParams) {
final genericTypes = getGenericTypes(type);
for (final genericType in genericTypes) {
if (alreadyEstablishedTypeParams
if (!alreadyEstablishedTypeParams
.any((al) => al.name == genericType.name)) {
types.add((genericType.name, genericType.constraint));
}
Expand All @@ -206,7 +205,7 @@ List<GenericType> getGenericTypes(Type t) {
for (final type in typeParams) {
final genericTypes = getGenericTypes(type);
for (final genericType in genericTypes) {
if (alreadyEstablishedTypeParams
if (!alreadyEstablishedTypeParams
.any((al) => al.name == genericType.name)) {
types.add((genericType.name, genericType.constraint));
}
Expand All @@ -219,17 +218,22 @@ List<GenericType> getGenericTypes(Type t) {
returnType: final closureType,
parameters: final closureParams
):
final typeParams = [closureType, ...closureParams.map((p) => p.type)]
.map(getGenericTypes)
.fold(<GenericType>[], (prev, combine) => [...prev, ...combine]);

types.addAll(typeParams.where((t) {
return alreadyEstablishedTypeParams.any((al) => al.name == t.name);
}).map((t) => (t.name, t.constraint)));
for (final type in [closureType, ...closureParams.map((p) => p.type)]) {
final genericTypes = getGenericTypes(type);
for (final genericType in genericTypes) {
if (!alreadyEstablishedTypeParams
.any((al) => al.name == genericType.name)) {
types.add((genericType.name, genericType.constraint));
}
}
}
break;
default:
break;
}

// Types are cloned so that modifications to constraints can happen without
// affecting initial references
return types.map((t) => GenericType(name: t.$1, constraint: t.$2)).toList();
}

Expand All @@ -241,11 +245,7 @@ Type desugarTypeAliases(Type t) {
return t;
}

Declaration generateTupleDeclaration(int count, {bool readonly = false}) {
return _TupleDeclaration(count: count, readonly: readonly);
}

class _TupleDeclaration extends NamedDeclaration
class TupleDeclaration extends NamedDeclaration
implements ExportableDeclaration {
@override
bool get exported => true;
Expand All @@ -257,7 +257,7 @@ class _TupleDeclaration extends NamedDeclaration

final bool readonly;

_TupleDeclaration({required this.count, this.readonly = false});
TupleDeclaration({required this.count, this.readonly = false});

@override
String? dartName;
Expand All @@ -270,6 +270,17 @@ class _TupleDeclaration extends NamedDeclaration
throw Exception('Forbidden: Cannot set name on tuple declaration');
}

/// Creates a tuple from types.
///
/// The type args represent the tuple types for the tuple declaration
@override
TupleType asReferredType(
[List<Type>? typeArgs, bool isNullable = false, String? url]) {
assert(typeArgs?.length == count,
'Type arguments must equal the number of tuples supported');
return TupleType(types: typeArgs ?? [], tupleDeclUrl: url);
}

@override
Spec emit([covariant DeclarationOptions? options]) {
options ??= DeclarationOptions();
Expand Down
76 changes: 33 additions & 43 deletions web_generator/lib/src/ast/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import 'declarations.dart';
import 'helpers.dart';

/// A type referring to a type in the TypeScript AST
class ReferredType<T extends Declaration> extends Type {
class ReferredType<T extends Declaration> extends NamedType {
@override
String name;

Expand Down Expand Up @@ -56,7 +56,8 @@ class ReferredDeclarationType<T extends Declaration> extends ReferredType<T> {
Type type;

@override
String get name => type.name ?? declaration.name;
String get name =>
type is NamedType ? (type as NamedType).name : declaration.name;

ReferredDeclarationType(this.type, T declaration,
{super.typeParams, super.url, super.isNullable})
Expand All @@ -72,28 +73,22 @@ class ReferredDeclarationType<T extends Declaration> extends ReferredType<T> {
}
}

class TupleType extends Type {
class TupleType extends ReferredType<TupleDeclaration> {
final List<Type> types;

@override
bool isNullable;
List<Type> get typeParams => types;

TupleType({required this.types, this.isNullable = false});
TupleType(
{required this.types, super.isNullable, required String? tupleDeclUrl})
: super(
declaration: TupleDeclaration(count: types.length),
name: 'JSTuple${types.length}',
url: tupleDeclUrl);

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

@override
String? get name => null;

@override
Reference emit([TypeOptions? options]) {
return TypeReference((t) => t
..symbol = 'JSTuple${types.length}'
..types.addAll(types.map((type) => type.emit(options)))
..isNullable = isNullable);
}

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

Expand All @@ -119,9 +114,6 @@ class UnionType extends DeclarationType {
@override
ID get id => ID(type: 'type', name: types.map((t) => t.id.name).join('|'));

@override
String? get name => null;

@override
Declaration get declaration => _UnionDeclaration(
name: declarationName, types: types, isNullable: isNullable);
Expand Down Expand Up @@ -174,7 +166,7 @@ class HomogenousEnumType<T extends LiteralType, D extends Declaration>
}

/// The base class for a type generic (like 'T')
class GenericType extends Type {
class GenericType extends NamedType {
@override
final String name;

Expand Down Expand Up @@ -219,7 +211,6 @@ class LiteralType extends Type {
@override
bool isNullable;

@override
String get name => switch (kind) {
LiteralKind.$null => 'null',
LiteralKind.int || LiteralKind.double => 'number',
Expand All @@ -246,7 +237,7 @@ class LiteralType extends Type {
}

@override
ID get id => ID(type: 'type', name: name);
ID get id => ID(type: 'type', name: '$name.$value');

@override
bool operator ==(Object other) {
Expand Down Expand Up @@ -289,9 +280,6 @@ class ObjectLiteralType extends DeclarationType<TypeDeclaration> {
@override
final String declarationName;

@override
String? get name => null;

@override
final ID id;

Expand Down Expand Up @@ -342,9 +330,6 @@ sealed class ClosureType extends DeclarationType {
@override
final ID id;

@override
String? get name => null;

ClosureType({
required String name,
required this.id,
Expand Down Expand Up @@ -517,14 +502,14 @@ class _UnionDeclaration extends NamedDeclaration
{required this.name,
this.types = const [],
this.isNullable = false,
List<GenericType>? typeParameters})
: typeParameters = typeParameters ?? [] {
if (typeParameters == null) {
List<GenericType>? typeParams})
: typeParameters = typeParams ?? [] {
if (typeParams == null) {
for (final type in types) {
this.typeParameters.addAll(getGenericTypes(type).map((t) {
t.constraint ??= BuiltinType.anyType;
return t;
}));
typeParameters.addAll(getGenericTypes(type).map((t) {
t.constraint ??= BuiltinType.anyType;
return t;
}));
}
}
}
Expand Down Expand Up @@ -555,9 +540,12 @@ class _UnionDeclaration extends NamedDeclaration
final type = t.emit(options?.toTypeOptions());
final jsTypeAlt = getJSTypeAlternative(t);
return Method((m) {
final word = t is DeclarationType
? t.declarationName
: (t.dartName ?? t.name ?? t.id.name);
final word = switch (t) {
DeclarationType(declarationName: final declName) => declName,
NamedType(name: final typeName, dartName: final dartTypeName) =>
dartTypeName ?? typeName,
_ => t.dartName ?? t.id.name
};
m
..type = MethodType.getter
..name = 'as${uppercaseFirstLetter(word)}'
Expand Down Expand Up @@ -588,11 +576,13 @@ class _UnionDeclaration extends NamedDeclaration
refer(n, url).property('_').call([
refer('_')
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
.property(switch (decl.baseType.name) {
'int' => 'toDartInt',
'num' || 'double' => 'toDartDouble',
_ => 'toDart'
})
.property(decl.baseType is NamedType
? switch ((decl.baseType as NamedType).name) {
'int' => 'toDartInt',
'num' || 'double' => 'toDartDouble',
_ => 'toDart'
}
: 'toDart')
]).code,
_ => refer('_')
.asA(jsTypeAlt.emit(options?.toTypeOptions()))
Expand Down
13 changes: 9 additions & 4 deletions web_generator/lib/src/dart_main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,19 @@ Future<void> generateJSInteropBindings(Config config) async {
if (generatedCodeMap.isEmpty) return;

// write code to file
if (generatedCodeMap.length == 1) {
final entry = generatedCodeMap.entries.first;
fs.writeFileSync(configOutput.toJS, entry.value.toJS);
} else {
if (dartDeclarations.multiFileOutput) {
for (final entry in generatedCodeMap.entries) {
fs.writeFileSync(
p.join(configOutput, p.basename(entry.key)).toJS, entry.value.toJS);
}
} else {
final entry = generatedCodeMap.entries.first;
fs.writeFileSync(configOutput.toJS, entry.value.toJS);
for (final entry in generatedCodeMap.entries.skip(1)) {
fs.writeFileSync(
p.join(p.dirname(configOutput), p.basename(entry.key)).toJS,
entry.value.toJS);
}
}
}

Expand Down
Loading