Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 3 additions & 1 deletion web_generator/lib/src/ast/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ abstract class Type extends Node {
@override
String? dartName;

/// Whether the given type is nullable or not
/// (unioned with `undefined` or `null`)
abstract bool isNullable;

@override
Expand Down Expand Up @@ -118,7 +120,7 @@ abstract class CallableDeclaration extends NamedDeclaration {

enum DeclScope { private, protected, public }

abstract class DeclarationAssociatedType<T extends Declaration> extends Type {
abstract class DeclarationType<T extends Declaration> extends Type {
T get declaration;

String get declarationName;
Expand Down
27 changes: 9 additions & 18 deletions web_generator/lib/src/ast/builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,14 @@ class BuiltinType extends Type {
final bool fromDartJSInterop;

@override
bool get isNullable => _isNullable ?? false;

final bool? _isNullable;

@override
set isNullable(bool isNullable) {}
bool isNullable;

BuiltinType(
{required this.name,
this.typeParams = const [],
this.fromDartJSInterop = false,
bool? isNullable})
: _isNullable = isNullable;
: isNullable = isNullable ?? false;

@override
ID get id => ID(type: 'type', name: name);
Expand All @@ -51,7 +46,7 @@ class BuiltinType extends Type {
.where((p) => typeParams.length != 1 || p != $voidType)
.map((p) => p.emit(options)))
..url = fromDartJSInterop ? 'dart:js_interop' : null
..isNullable = _isNullable ?? options?.nullable);
..isNullable = isNullable || (options?.nullable ?? false));
}

static final BuiltinType $voidType = BuiltinType(name: 'void');
Expand Down Expand Up @@ -141,12 +136,7 @@ class PackageWebType extends Type {
final List<Type> typeParams;

@override
bool get isNullable => _isNullable ?? false;

final bool? _isNullable;

@override
set isNullable(bool isNullable) {}
bool isNullable;

@override
ID get id => ID(type: 'type', name: name);
Expand All @@ -155,8 +145,9 @@ class PackageWebType extends Type {
String? get dartName => null;

PackageWebType._(
{required this.name, this.typeParams = const [], bool? isNullable})
: _isNullable = isNullable;
{required this.name,
this.typeParams = const [],
this.isNullable = false});

@override
Reference emit([TypeOptions? options]) {
Expand All @@ -169,14 +160,14 @@ class PackageWebType extends Type {
.where((p) => typeParams.length != 1 || p != BuiltinType.$voidType)
.map((p) => p.emit(options)))
..url = 'package:web/web.dart'
..isNullable = _isNullable ?? options?.nullable);
..isNullable = isNullable || (options?.nullable ?? false));
}

static PackageWebType parse(String name,
{bool? isNullable, List<Type> typeParams = const []}) {
return PackageWebType._(
name: renameMap.containsKey(name) ? renameMap[name]! : name,
isNullable: isNullable,
isNullable: isNullable ?? false,
typeParams: typeParams);
}
}
Expand Down
13 changes: 7 additions & 6 deletions web_generator/lib/src/ast/declarations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,14 @@ sealed class TypeDeclaration extends NestableDeclaration
this.constructors = const [],
this.parent});

/// [assertRepType] is used to assert that the extension type generated
/// has a representation type of the first member of [extendees] if any.
/// [useFirstExtendeeAsRepType] is used to assert that the extension type
/// generated has a representation type of the first member of [extendees]
/// if any.
ExtensionType _emit(covariant DeclarationOptions? options,
{bool abstract = false,
List<Type> extendees = const [],
List<Type> implementees = const [],
bool assertRepType = false,
bool useFirstExtendeeAsRepType = false,
bool objectLiteralConstructor = false}) {
options ??= DeclarationOptions();

Expand All @@ -96,8 +97,8 @@ sealed class TypeDeclaration extends NestableDeclaration
methodDecs.addAll(operators.where((p) => p.scope == DeclScope.public).map(
(m) => m.emit(options!..override = isOverride(m.dartName ?? m.name))));

final repType = assertRepType || this is ClassDeclaration
? getClassRepresentationType(this)
final repType = useFirstExtendeeAsRepType || this is ClassDeclaration
? getRepresentationType(this)
: BuiltinType.primitiveType(PrimitiveType.object, isNullable: false);

return ExtensionType((e) => e
Expand Down Expand Up @@ -637,7 +638,7 @@ class InterfaceDeclaration extends TypeDeclaration {
ExtensionType emit([covariant DeclarationOptions? options]) {
return super._emit(options,
extendees: extendedTypes,
assertRepType: assertRepType,
useFirstExtendeeAsRepType: assertRepType,
objectLiteralConstructor: objectLiteralConstructor);
}
}
Expand Down
75 changes: 42 additions & 33 deletions web_generator/lib/src/ast/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,18 @@ Set<String> getMemberHierarchy(TypeDeclaration type,
return members;
}

Type getClassRepresentationType(TypeDeclaration cl) {
Type getRepresentationType(TypeDeclaration cl) {
if (cl case ClassDeclaration(extendedType: final extendee?)) {
return switch (extendee) {
ReferredType(declaration: final d) when d is ClassDeclaration =>
getClassRepresentationType(d),
getRepresentationType(d),
final BuiltinType b => b,
_ => BuiltinType.primitiveType(PrimitiveType.object, isNullable: false)
};
} else if (cl case InterfaceDeclaration(extendedTypes: [final extendee])) {
return switch (extendee) {
ReferredType(declaration: final d) when d is ClassDeclaration =>
getClassRepresentationType(d),
ReferredType(declaration: final d) when d is TypeDeclaration =>
getRepresentationType(d),
final BuiltinType b => b,
_ => BuiltinType.primitiveType(PrimitiveType.object, isNullable: false)
};
Expand Down Expand Up @@ -143,6 +143,7 @@ Type getClassRepresentationType(TypeDeclaration cl) {
return (requiredParams, optionalParams);
}

/// Recursively get the generic types specified in a given type [t]
List<GenericType> getGenericTypes(Type t) {
final types = <(String, Type?)>[];
switch (t) {
Expand All @@ -152,10 +153,10 @@ List<GenericType> getGenericTypes(Type t) {
case ReferredType(typeParams: final referredTypeParams):
case UnionType(types: final referredTypeParams):
case TupleType(types: final referredTypeParams):
types.addAll(referredTypeParams
.map(getGenericTypes)
.fold(<GenericType>[], (prev, combine) => [...prev, ...combine]).map(
(t) => (t.name, t.constraint)));
for (final referredTypeParam in referredTypeParams) {
types.addAll(getGenericTypes(referredTypeParam)
.map((t) => (t.name, t.constraint)));
}
break;
case ObjectLiteralType(
properties: final objectProps,
Expand All @@ -173,36 +174,44 @@ List<GenericType> getGenericTypes(Type t) {
returnType: methodType,
parameters: methodParams
) in objectMethods) {
final typeParams = [methodType, ...methodParams.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)));
final typeParams = [methodType, ...methodParams.map((p) => p.type)];

for (final type in typeParams) {
final genericTypes = getGenericTypes(type);
for (final genericType in genericTypes) {
if (alreadyEstablishedTypeParams
.any((al) => al.name == genericType.name)) {
types.add((genericType.name, genericType.constraint));
}
}
}
}

for (final ConstructorDeclaration(parameters: methodParams)
in objectConstructors) {
types.addAll(methodParams.map((p) => getGenericTypes(p.type)).fold(
<GenericType>[],
(prev, combine) =>
[...prev, ...combine]).map((t) => (t.name, t.constraint)));
for (final ParameterDeclaration(type: methodParamType)
in methodParams) {
types.addAll(getGenericTypes(methodParamType)
.map((t) => (t.name, t.constraint)));
}
}

for (final OperatorDeclaration(
typeParameters: alreadyEstablishedTypeParams,
returnType: methodType,
parameters: methodParams
) in objectOperators) {
final typeParams = [methodType, ...methodParams.map((p) => p.type)]
.map(getGenericTypes)
.fold(<GenericType>[], (prev, combine) => [...prev, ...combine]);

types.addAll(typeParams.where((t) {
return !alreadyEstablishedTypeParams.contains(t) ||
alreadyEstablishedTypeParams.any((al) => al.name == t.name);
}).map((t) => (t.name, t.constraint)));
final typeParams = [methodType, ...methodParams.map((p) => p.type)];

for (final type in typeParams) {
final genericTypes = getGenericTypes(type);
for (final genericType in genericTypes) {
if (alreadyEstablishedTypeParams
.any((al) => al.name == genericType.name)) {
types.add((genericType.name, genericType.constraint));
}
}
}
}
break;
case ClosureType(
Expand All @@ -224,10 +233,10 @@ List<GenericType> getGenericTypes(Type t) {
return types.map((t) => GenericType(name: t.$1, constraint: t.$2)).toList();
}

Type getDeepType(Type t) {
Type desugarTypeAliases(Type t) {
if (t case final ReferredType ref
when ref.declaration is TypeAliasDeclaration) {
return getDeepType((ref.declaration as TypeAliasDeclaration).type);
return desugarTypeAliases((ref.declaration as TypeAliasDeclaration).type);
}
return t;
}
Expand Down Expand Up @@ -257,16 +266,16 @@ class _TupleDeclaration extends NamedDeclaration
String get name => readonly ? 'JSReadonlyTuple$count' : 'JSTuple$count';

@override
set name(String name) {}
set name(String name) {
throw Exception('Forbidden: Cannot set name on tuple declaration');
}

@override
Spec emit([covariant DeclarationOptions? options]) {
options ??= DeclarationOptions();

final repType = BuiltinType.primitiveType(PrimitiveType.array,
shouldEmitJsType: true,
// TODO: get sub type instead
typeParams: [BuiltinType.anyType]);
shouldEmitJsType: true, typeParams: [BuiltinType.anyType]);

return ExtensionType((e) => e
..name = name
Expand Down
36 changes: 21 additions & 15 deletions web_generator/lib/src/ast/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class TupleType extends Type {
}
}

class UnionType extends DeclarationAssociatedType {
class UnionType extends DeclarationType {
final List<Type> types;

@override
Expand Down Expand Up @@ -143,7 +143,7 @@ class UnionType extends DeclarationAssociatedType {
}

class HomogenousEnumType<T extends LiteralType, D extends Declaration>
extends UnionType implements DeclarationAssociatedType {
extends UnionType implements DeclarationType {
final List<T> _types;

@override
Expand Down Expand Up @@ -274,7 +274,7 @@ enum LiteralKind {
};
}

class ObjectLiteralType extends DeclarationAssociatedType<TypeDeclaration> {
class ObjectLiteralType extends DeclarationType<TypeDeclaration> {
final List<PropertyDeclaration> properties;

final List<MethodDeclaration> methods;
Expand Down Expand Up @@ -316,9 +316,8 @@ class ObjectLiteralType extends DeclarationAssociatedType<TypeDeclaration> {
operators: operators,
constructors: constructors,
typeParameters: getGenericTypes(this).map((g) {
final t = g;
t.constraint ??= BuiltinType.anyType;
return t;
g.constraint ??= BuiltinType.anyType;
return g;
}).toList());

@override
Expand All @@ -330,7 +329,7 @@ class ObjectLiteralType extends DeclarationAssociatedType<TypeDeclaration> {
}
}

sealed class ClosureType extends DeclarationAssociatedType {
sealed class ClosureType extends DeclarationType {
final List<ParameterDeclaration> parameters;
final Type returnType;
final List<GenericType> typeParameters;
Expand Down Expand Up @@ -445,8 +444,10 @@ class _ConstructorDeclaration extends CallableDeclaration

final repType = BuiltinType.referred('Function')!;

final isNamedParams = getDeepType(returnType) is ObjectLiteralType &&
(getDeepType(returnType) as ObjectLiteralType).constructors.isEmpty;
final isNamedParams = desugarTypeAliases(returnType) is ObjectLiteralType &&
(desugarTypeAliases(returnType) as ObjectLiteralType)
.constructors
.isEmpty;

return ExtensionType((eType) => eType
..name = name
Expand Down Expand Up @@ -517,12 +518,16 @@ class _UnionDeclaration extends NamedDeclaration
this.types = const [],
this.isNullable = false,
List<GenericType>? typeParameters})
: typeParameters = typeParameters ??
types.map(getGenericTypes).fold(<GenericType>[],
(prev, combine) => [...prev, ...combine]).map((t) {
: typeParameters = typeParameters ?? [] {
if (typeParameters == null) {
for (final type in types) {
this.typeParameters.addAll(getGenericTypes(type).map((t) {
t.constraint ??= BuiltinType.anyType;
return t;
}).toList();
}));
}
}
}

@override
String? dartName;
Expand All @@ -534,7 +539,8 @@ class _UnionDeclaration extends NamedDeclaration
Spec emit([covariant DeclarationOptions? options]) {
options ??= DeclarationOptions();

final repType = getSubTypeOfTypes(types, isNullable: isNullable);
final repType =
getLowestCommonAncestorOfTypes(types, isNullable: isNullable);

return ExtensionType((e) => e
..name = name
Expand All @@ -549,7 +555,7 @@ class _UnionDeclaration extends NamedDeclaration
final type = t.emit(options?.toTypeOptions());
final jsTypeAlt = getJSTypeAlternative(t);
return Method((m) {
final word = t is DeclarationAssociatedType
final word = t is DeclarationType
? t.declarationName
: (t.dartName ?? t.name ?? t.id.name);
m
Expand Down
4 changes: 4 additions & 0 deletions web_generator/lib/src/interop_gen/hasher.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';

import 'package:convert/convert.dart';
Expand Down
Loading
Loading