Skip to content

Commit d693fb8

Browse files
committed
Bridge bindgen working correctly
1 parent 952e43d commit d693fb8

File tree

12 files changed

+267
-36
lines changed

12 files changed

+267
-36
lines changed

bin/dart_eval.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ void main(List<String> args) {
2525
bindCmd.addFlag('help', abbr: 'h');
2626
bindCmd.addFlag('single-file', abbr: 's');
2727
bindCmd.addFlag('all', abbr: 'a');
28+
bindCmd.addFlag('plugin', defaultsTo: true);
2829

2930
// ignore: unused_local_variable
3031
final helpCmd = parser.addCommand('help');
@@ -110,12 +111,12 @@ void main(List<String> args) {
110111
runtime.printOpcodes();
111112
} else if (command.name == 'bind') {
112113
if (command['help']!) {
113-
print('bind: Generate bindings for a Dart project (experimental)');
114+
print('bind: Generate bindings for a Dart project');
114115
print('Usage:');
115-
print(' dart_eval bind [-h, --help] [-a, --all] [-s, --single-file]');
116+
print(' dart_eval bind [-h, --help] [-a, --all] [-s, --single-file] [--[no-]plugin]');
116117
exit(0);
117118
}
118119

119-
cliBind(command['single-file'], command['all']);
120+
cliBind(singleFile: command['single-file'], all: command['all'], generatePlugin: command['plugin']);
120121
}
121122
}

lib/src/eval/bindgen/bindgen.dart

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:analyzer/dart/ast/ast.dart';
44
import 'package:analyzer/dart/element/element2.dart';
55
import 'package:analyzer/file_system/physical_file_system.dart';
66
import 'package:collection/collection.dart';
7+
import 'package:dart_eval/dart_eval_bridge.dart';
78
import 'package:dart_eval/src/eval/bindgen/bridge.dart';
89
import 'package:dart_eval/src/eval/bindgen/bridge_declaration.dart';
910
import 'package:dart_eval/src/eval/bindgen/configure.dart';
@@ -19,10 +20,14 @@ import 'package:package_config/package_config.dart';
1920
import 'package:path/path.dart';
2021

2122
/// Adapted from code by Alex Wallen (@a-wallen)
22-
class Bindgen {
23+
class Bindgen implements BridgeDeclarationRegistry {
2324
static final resourceProvider = PhysicalResourceProvider.INSTANCE;
2425
final includedPaths = [resourceProvider.pathContext.current];
2526

27+
final _bridgeDeclarations = <String, List<BridgeDeclaration>>{};
28+
final _exportedLibMappings = <String, String>{};
29+
final List<({String file, String uri, String name})> registerClasses = [];
30+
2631
AnalysisContextCollection? _contextCollection;
2732

2833
void inject({required Package package}) {
@@ -35,6 +40,73 @@ class Bindgen {
3540
includedPaths.add(normalize(filepath));
3641
}
3742

43+
// Manually define a (unresolved) bridge class
44+
@override
45+
void defineBridgeClass(BridgeClassDef classDef) {
46+
if (!classDef.bridge && !classDef.wrap) {
47+
throw CompileError(
48+
'Cannot define a bridge class that\'s not either bridge or wrap');
49+
}
50+
final type = classDef.type;
51+
final spec = type.type.spec;
52+
53+
if (spec == null) {
54+
throw CompileError(
55+
'Cannot define a bridge class that\'s already resolved, a ref, or a generic function type');
56+
}
57+
58+
final libraryDeclarations = _bridgeDeclarations[spec.library];
59+
if (libraryDeclarations == null) {
60+
_bridgeDeclarations[spec.library] = [classDef];
61+
} else {
62+
libraryDeclarations.add(classDef);
63+
}
64+
}
65+
66+
/// Define a bridged enum definition to be used when binding.
67+
@override
68+
void defineBridgeEnum(BridgeEnumDef enumDef) {
69+
final spec = enumDef.type.spec;
70+
if (spec == null) {
71+
throw CompileError(
72+
'Cannot define a bridge enum that\'s already resolved, a ref, or a generic function type');
73+
}
74+
75+
final libraryDeclarations = _bridgeDeclarations[spec.library];
76+
if (libraryDeclarations == null) {
77+
_bridgeDeclarations[spec.library] = [enumDef];
78+
} else {
79+
libraryDeclarations.add(enumDef);
80+
}
81+
}
82+
83+
@override
84+
void addSource(DartSource source) {
85+
// Has no effect in binding generator
86+
}
87+
88+
/// Define a bridged top-level function declaration.
89+
@override
90+
void defineBridgeTopLevelFunction(BridgeFunctionDeclaration function) {
91+
final libraryDeclarations = _bridgeDeclarations[function.library];
92+
if (libraryDeclarations == null) {
93+
_bridgeDeclarations[function.library] = [function];
94+
} else {
95+
libraryDeclarations.add(function);
96+
}
97+
}
98+
99+
/// Define a set of unresolved bridge classes
100+
void defineBridgeClasses(List<BridgeClassDef> classDefs) {
101+
for (final classDef in classDefs) {
102+
defineBridgeClass(classDef);
103+
}
104+
}
105+
106+
void addExportedLibraryMapping(String libraryUri, String exportUri) {
107+
_exportedLibMappings[libraryUri] = exportUri;
108+
}
109+
38110
Future<String?> parse(
39111
io.File src, String filename, String uri, bool all) async {
40112
final resourceProvider = PhysicalResourceProvider.INSTANCE;
@@ -50,7 +122,12 @@ class Bindgen {
50122
final analysisContext = _contextCollection!.contextFor(filePath);
51123
final session = analysisContext.currentSession;
52124
final analysisResult = await session.getResolvedUnit(filePath);
53-
final ctx = BindgenContext(uri, all: all);
125+
final ctx = BindgenContext(
126+
filename,
127+
uri,
128+
all: all,
129+
bridgeDeclarations: _bridgeDeclarations,
130+
exportedLibMappings: _exportedLibMappings);
54131

55132
if (analysisResult is ResolvedUnitResult) {
56133
// Access the resolved unit and analyze it
@@ -82,6 +159,7 @@ class Bindgen {
82159
.where((declaration) => declaration.declaredFragment != null)
83160
.map((declaration) =>
84161
_$instance(ctx, declaration.declaredFragment!.element))
162+
.toList()
85163
.nonNulls;
86164

87165
if (resolved.isEmpty) {
@@ -123,16 +201,24 @@ class Bindgen {
123201

124202
final isBridge = bindAnnoValue?.getField('bridge')?.toBoolValue() ?? false;
125203

204+
if (element.isSealed) {
205+
throw CompileError(
206+
'Cannot bind sealed class ${element.name3} as a bridge type. '
207+
'Please remove the @Bind annotation, use a wrapper, or make the class non-sealed.');
208+
}
209+
210+
registerClasses.add((
211+
file: ctx.filename,
212+
uri: ctx.libOverrides[element.name3!] ?? ctx.uri,
213+
name: '${element.name3!}${isBridge ? '\$bridge' : ''}',
214+
));
215+
126216
if (isBridge) {
127-
if (element.isSealed) {
128-
throw CompileError(
129-
'Cannot bind sealed class ${element.name3} as a bridge type. '
130-
'Please remove the @Bind annotation, use a wrapper, or make the class non-sealed.');
131-
}
132217

133218
return '''
134219
/// dart_eval bridge binding for [${element.name3}]
135220
class \$${element.name3}\$bridge extends ${element.name3} with \$Bridge<${element.name3}> {
221+
${bindForwardedConstructors(ctx, element)}
136222
/// Configure this class for use in a [Runtime]
137223
${bindConfigureForRuntime(ctx, element, isBridge: true)}
138224
/// Compile-time type specification of [\$${element.name3}\$bridge]

lib/src/eval/bindgen/bridge.dart

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,31 @@ import 'package:analyzer/dart/element/type.dart';
44
import 'package:dart_eval/src/eval/bindgen/context.dart';
55
import 'package:dart_eval/src/eval/bindgen/type.dart';
66

7+
String bindForwardedConstructors(
8+
BindgenContext ctx, ClassElement2 element,
9+
{bool isBridge = false}) {
10+
return element.constructors2
11+
.where((cstr) => !cstr.isPrivate)
12+
.map((e) => _$forwardedConstructor(ctx, element, e, isBridge: isBridge))
13+
.join('\n');
14+
}
15+
16+
String _$forwardedConstructor(
17+
BindgenContext ctx, ClassElement2 element, ConstructorElement2 constructor,
18+
{bool isBridge = false}) {
19+
final name = constructor.name3 == null ? '' : constructor.name3;
20+
final namedConstructor =
21+
constructor.name3 != null && constructor.name3 != 'new'
22+
? '.${constructor.name3}'
23+
: '';
24+
final fullyQualifiedConstructorId = '\$${element.name3}\$bridge$namedConstructor';
25+
26+
return '''
27+
/// Forwarded constructor for [${element.name3}.$name]
28+
$fullyQualifiedConstructorId(${parameterHeader(constructor.formalParameters, forConstructor: true)});
29+
''';
30+
}
31+
732
String bindDecoratoratorMethods(BindgenContext ctx, ClassElement2 element) {
833
final methods = {
934
if (ctx.implicitSupers)
@@ -26,15 +51,15 @@ String bindDecoratoratorMethods(BindgenContext ctx, ClassElement2 element) {
2651

2752
return '''
2853
@override
29-
${returnType} ${e.displayName}(${_parameterHeader(e.formalParameters)}) =>
54+
${returnType} ${e.displayName}(${parameterHeader(e.formalParameters)}) =>
3055
${needsCast ? '(' : ''}\$_invoke('${e.displayName}', [
3156
${e.formalParameters.map((p) => wrapVar(ctx, p.type, p.name3 ?? '')).join(', ')}
3257
])${needsCast ? 'as ${returnType.element3!.name3}$q)$q.cast()' : ''};
3358
''';
3459
}).join('\n');
3560
}
3661

37-
String _parameterHeader(List<FormalParameterElement> params) {
62+
String parameterHeader(List<FormalParameterElement> params, {bool forConstructor = false}) {
3863
final paramBuffer = StringBuffer();
3964
var inNonPositional = false;
4065
for (var i = 0; i < params.length; i++) {
@@ -45,15 +70,23 @@ String _parameterHeader(List<FormalParameterElement> params) {
4570
paramBuffer.write(param.isNamed ? '{' : '[');
4671
}
4772
}
73+
if (param.isRequiredNamed) {
74+
paramBuffer.write('required ');
75+
}
4876
switch (param.type) {
4977
case FunctionType functionType:
5078
paramBuffer.write(functionType.returnType.getDisplayString());
5179
paramBuffer.write(' Function(');
52-
paramBuffer.write(_parameterHeader(functionType.formalParameters));
80+
paramBuffer.write(parameterHeader(functionType.formalParameters));
5381
paramBuffer.write(')');
5482
break;
5583
default:
56-
paramBuffer.write('${param.type.getDisplayString()} ${param.name3}');
84+
if (forConstructor) {
85+
paramBuffer.write('super.');
86+
} else {
87+
paramBuffer.write('${param.type.getDisplayString()} ');
88+
}
89+
paramBuffer.write('${param.name3}');
5790
}
5891
if (i < params.length - 1) {
5992
paramBuffer.write(', ');

lib/src/eval/bindgen/bridge_declaration.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ String fields(BindgenContext ctx, ClassElement2 element) {
157157
String bridgeConstructorDef(BindgenContext ctx,
158158
{required ConstructorElement2 constructor}) {
159159
return '''
160-
'${constructor.name3}': BridgeConstructorDef(
160+
'${constructor.name3 == 'new' ? '' : constructor.name3}': BridgeConstructorDef(
161161
BridgeFunctionDef(
162162
returns: BridgeTypeAnnotation(\$type),
163163
namedParams: [${namedParameters(ctx, element: constructor)}],

lib/src/eval/bindgen/configure.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ String constructorsForRuntime(BindgenContext ctx, ClassElement2 element,
2424
String constructorForRuntime(
2525
BindgenContext ctx, ClassElement2 element, ConstructorElement2 constructor,
2626
{bool isBridge = false}) {
27-
final name = constructor.name3 == null ? '' : constructor.name3;
27+
var name = constructor.name3 == null ? '' : constructor.name3;
28+
if (name == 'new') {
29+
name = '';
30+
}
2831
final fullyQualifiedConstructorId = '${element.name3}.$name';
2932

30-
final staticName = constructor.name3 == null ? 'new' : constructor.name3;
33+
final staticName = constructor.name3 == null ? '' : constructor.name3;
3134
final uri = ctx.libOverrides[element.name3] ?? ctx.uri;
32-
final bridgeParam = isBridge ? ',bridge: true' : '';
35+
final bridgeParam = isBridge ? ', isBridge: true' : '';
3336

3437
return '''
3538
runtime.registerBridgeFunc(

lib/src/eval/bindgen/context.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import 'package:dart_eval/dart_eval_bridge.dart';
2+
13
class BindgenContext {
4+
final String filename;
25
final String uri;
36
final Set<String> imports = {};
47
final Set<String> knownTypes = {};
58
final Set<String> unknownTypes = {};
69
final bool all;
710
final Map<String, String> libOverrides = {};
811
bool implicitSupers = false;
12+
final Map<String, List<BridgeDeclaration>> bridgeDeclarations;
13+
final Map<String, String> exportedLibMappings;
914

10-
BindgenContext(this.uri, {required this.all});
15+
BindgenContext(this.filename, this.uri, {required this.all, required this.bridgeDeclarations, required this.exportedLibMappings});
1116
}

lib/src/eval/bindgen/statics.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@ import 'package:dart_eval/src/eval/bindgen/type.dart';
77
String $constructors(BindgenContext ctx, ClassElement2 element,
88
{bool isBridge = false}) {
99
return element.constructors2
10-
.where((cstr) =>
11-
!cstr.isPrivate &&
12-
!cstr.isSynthetic &&
13-
(cstr.isFactory || !element.isAbstract))
10+
.where(
11+
(cstr) => !cstr.isPrivate && (cstr.isFactory || !element.isAbstract))
1412
.map((e) => _$constructor(ctx, element, e, isBridge: isBridge))
1513
.join('\n');
1614
}
1715

1816
String _$constructor(
1917
BindgenContext ctx, ClassElement2 element, ConstructorElement2 constructor,
2018
{bool isBridge = false}) {
21-
final name = constructor.name3 == null ? 'new' : constructor.name3;
19+
final name = constructor.name3 == null ? '' : constructor.name3;
2220
final namedConstructor =
23-
constructor.name3 != null ? '.${constructor.name3}' : '';
21+
constructor.name3 != null && constructor.name3 != 'new'
22+
? '.${constructor.name3}'
23+
: '';
2424
final fullyQualifiedConstructorId = isBridge
2525
? '\$${element.name3}\$bridge$namedConstructor'
2626
: '${element.name3}$namedConstructor';

lib/src/eval/bindgen/type.dart

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:collection/collection.dart';
66
import 'package:dart_eval/src/eval/bindgen/context.dart';
77
import 'package:dart_eval/src/eval/bindgen/errors.dart';
88
import 'package:dart_eval/src/eval/bindgen/parameters.dart';
9+
import 'package:path/path.dart' as path;
910

1011
String bridgeTypeRefFromType(BindgenContext ctx, DartType type) {
1112
if (type is TypeParameterType) {
@@ -21,8 +22,9 @@ String bridgeTypeRefFromType(BindgenContext ctx, DartType type) {
2122
],
2223
))''';
2324
} else if (type is ParameterizedType) {
24-
final typeArgs =
25-
type.typeArguments.map((e) => bridgeTypeAnnotationFrom(ctx, e)).join(', ');
25+
final typeArgs = type.typeArguments
26+
.map((e) => bridgeTypeAnnotationFrom(ctx, e))
27+
.join(', ');
2628
return 'BridgeTypeRef(${bridgeTypeSpecFrom(ctx, type)}, [$typeArgs])';
2729
}
2830
return 'BridgeTypeRef(${bridgeTypeSpecFrom(ctx, type)})';
@@ -211,12 +213,32 @@ String? wrapType(BindgenContext ctx, DartType type, String expr,
211213
}
212214

213215
final typeEl = type.element3!;
214-
if (typeEl is ClassElement2 &&
215-
typeEl.metadata2.annotations
216-
.any((e) => e.element2?.displayName == 'Bind')) {
217-
ctx.imports
218-
.add(typeEl.library2.uri.toString().replaceAll('.dart', '.eval.dart'));
219-
return '${unionStr}\$$name.wrap($expr)';
216+
if (typeEl is ClassElement2) {
217+
final uri = typeEl.library2.uri.toString();
218+
final hasAnno = typeEl.metadata2.annotations
219+
.any((e) => e.element2?.displayName == 'Bind');
220+
if (hasAnno) {
221+
ctx.imports.add(uri.replaceAll('.dart', '.eval.dart'));
222+
return '${unionStr}\$$name.wrap($expr)';
223+
} else if (ctx.bridgeDeclarations.containsKey(uri)) {
224+
final parsedUri = Uri.parse(uri);
225+
226+
String current = parsedUri.path;
227+
String? mappedUri;
228+
// walk up the path until we find a match in ctx.exportedLibMappings
229+
while (current != path.dirname(current)) {
230+
if (ctx.exportedLibMappings.containsKey('${parsedUri.scheme}:$current')) {
231+
mappedUri = ctx.exportedLibMappings['${parsedUri.scheme}:$current']!;
232+
break;
233+
}
234+
current = path.dirname(current);
235+
}
236+
237+
if (mappedUri != null) {
238+
ctx.imports.add(mappedUri);
239+
return '${unionStr}\$$name.wrap($expr)';
240+
}
241+
}
220242
}
221243

222244
if (type is TypeParameterType) {

lib/src/eval/bridge/registry.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ abstract class BridgeDeclarationRegistry {
1515

1616
/// Define a bridged top-level function declaration.
1717
void defineBridgeTopLevelFunction(BridgeFunctionDeclaration function);
18+
19+
/// Add a mapping from a library URI to an exported library URI.
20+
void addExportedLibraryMapping(String libraryUri, String exportUri);
1821
}

0 commit comments

Comments
 (0)