Skip to content

Commit

Permalink
writeback List.view(), fix empty map error
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanblake4 committed Feb 21, 2024
1 parent e297975 commit 3da8fe2
Show file tree
Hide file tree
Showing 15 changed files with 473 additions and 32 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.7.5
- Create writeback-capable List wrapper. Use `$List.view()` with a type mapper
function to create a view of an underlying List that can be written to.
- Support for `$List.view` in the binding generator
- Binding generator can now generate `configureForRuntime` methods
- Support for static methods and getters in the binding generator
- Fix compile error with empty map literals

## 0.7.4
- Fix return value boxing of equals and not equals operators
- Fix assigning enums in conditional expressions
Expand Down
17 changes: 13 additions & 4 deletions lib/src/eval/bindgen/bindgen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:collection/collection.dart';
import 'package:dart_eval/src/eval/bindgen/bridge_declaration.dart';
import 'package:dart_eval/src/eval/bindgen/configure.dart';
import 'package:dart_eval/src/eval/bindgen/context.dart';
import 'package:dart_eval/src/eval/bindgen/statics.dart';
import 'package:dart_eval/src/eval/bindgen/type.dart';
Expand Down Expand Up @@ -61,7 +62,10 @@ class Bindgen {
}

final result = resolved.join('\n');
final imports = ctx.imports.map((e) => 'import \'$e\';').join('\n');
final imports = ctx.imports
.whereNot((e) => e == uri)
.map((e) => 'import \'$e\';')
.join('\n');

return imports + result;
}
Expand All @@ -82,11 +86,15 @@ class Bindgen {
return '''
/// dart_eval wrapper binding for [${element.name}]
class \$${element.name} implements \$Instance {
/// Configure this class for use in a [Runtime]
${bindConfigureForRuntime(ctx, element)}
/// Compile-time type declaration of [\$${element.name}]
${bindBridgeType(ctx, element)}
/// Compile-time class declaration of [\$${element.name}]
${bindBridgeDeclaration(ctx, element)}
${$constructors(element)}
${$staticMethods(ctx, element)}
${$staticGetters(ctx, element)}
${$wrap(ctx, element)}
${$getRuntimeType(element)}
${$getProperty(ctx, element)}
Expand All @@ -105,7 +113,7 @@ ${$setProperty(ctx, element)}
}

String $wrap(BindgenContext ctx, ClassElement element) {
return '''
return '''
final \$Instance _superclass;
@override
Expand All @@ -130,8 +138,9 @@ ${$setProperty(ctx, element)}
}

String propertyGetters(BindgenContext ctx, ClassElement element) {
final _getters = element.accessors.where((element) => element.isGetter);
final _methods = element.methods;
final _getters = element.accessors
.where((accessor) => accessor.isGetter && !accessor.isStatic);
final _methods = element.methods.where((method) => !method.isStatic);
if (_getters.isEmpty && _methods.isEmpty) {
return '';
}
Expand Down
68 changes: 68 additions & 0 deletions lib/src/eval/bindgen/configure.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:dart_eval/src/eval/bindgen/context.dart';

String bindConfigureForRuntime(BindgenContext ctx, ClassElement element) => '''
static void configureForRuntime(Runtime runtime) {
${constructorsForRuntime(ctx, element)}
${staticMethodsForRuntime(ctx, element)}
${staticGettersForRuntime(ctx, element)}
}
''';

String constructorsForRuntime(BindgenContext ctx, ClassElement element) {
return element.constructors
.map((e) => constructorForRuntime(ctx, element, e))
.join('\n');
}

String constructorForRuntime(
BindgenContext ctx, ClassElement element, ConstructorElement constructor) {
final name = constructor.name.isEmpty ? '' : constructor.name;
final fullyQualifiedConstructorId = '${element.name}.$name';

final staticName = constructor.name.isEmpty ? 'new' : constructor.name;

return '''
runtime.registerBridgeFunc(
'${ctx.uri}',
'$fullyQualifiedConstructorId',
\$${element.name}.\$$staticName
);
''';
}

String staticMethodsForRuntime(BindgenContext ctx, ClassElement element) {
return element.methods
.where((e) => e.isStatic)
.map((e) => staticMethodForRuntime(ctx, element, e))
.join('\n');
}

String staticMethodForRuntime(
BindgenContext ctx, ClassElement element, MethodElement method) {
return '''
runtime.registerBridgeFunc(
'${ctx.uri}',
'${element.name}.${method.name}',
\$${element.name}.\$${method.name}
);
''';
}

String staticGettersForRuntime(BindgenContext ctx, ClassElement element) {
return element.accessors
.where((e) => e.isStatic && e.isGetter)
.map((e) => staticGetterForRuntime(ctx, element, e))
.join('\n');
}

String staticGetterForRuntime(
BindgenContext ctx, ClassElement element, PropertyAccessorElement getter) {
return '''
runtime.registerBridgeFunc(
'${ctx.uri}',
'${element.name}.${getter.name}*g',
\$${element.name}.\$${getter.name}
);
''';
}
2 changes: 2 additions & 0 deletions lib/src/eval/bindgen/context.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class BindgenContext {
final String uri;
final Set<String> imports = {};
final Set<String> knownTypes = {};
final Set<String> unknownTypes = {};
final bool wrap;
final bool all;

Expand Down
12 changes: 12 additions & 0 deletions lib/src/eval/bindgen/parameters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ String _parameterFrom(ParameterElement parameter) {
),
''';
}

String argumentAccessors(List<ParameterElement> parameters) {
return parameters.asMap().entries.map((e) {
final index = e.key;
final parameter = e.value;
final nullCheck = parameter.isRequired ? '!' : '?';
final defaultValue =
parameter.hasDefaultValue ? ' ?? ${parameter.defaultValueCode}' : '';
final paramName = parameter.isNamed ? '${parameter.name}: ' : '';
return '${paramName}args[$index]$nullCheck.\$value$defaultValue';
}).join(',\n');
}
51 changes: 42 additions & 9 deletions lib/src/eval/bindgen/statics.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:dart_eval/src/eval/bindgen/context.dart';
import 'package:dart_eval/src/eval/bindgen/parameters.dart';
import 'package:dart_eval/src/eval/bindgen/type.dart';

String $constructors(ClassElement element) {
return element.constructors.map((e) => _$constructor(element, e)).join('\n');
Expand All @@ -15,17 +18,47 @@ String _$constructor(ClassElement element, ConstructorElement constructor) {
static \$Value? \$$name(Runtime runtime, \$Value? thisValue, List<\$Value?> args) {
return \$${element.name}.wrap(
$fullyQualifiedConstructorId(
${constructor.parameters.asMap().entries.map((e) {
final index = e.key;
final parameter = e.value;
final nullCheck = parameter.isRequired ? '!' : '?';
final defaultValue =
parameter.hasDefaultValue ? ' ?? ${parameter.defaultValueCode}' : '';
final paramName = parameter.isNamed ? '${parameter.name}: ' : '';
return '${paramName}args[$index]$nullCheck.\$value$defaultValue';
}).join(',\n')}
${argumentAccessors(constructor.parameters)}
),
);
}
''';
}

String $staticMethods(BindgenContext ctx, ClassElement element) {
return element.methods
.where((e) => e.isStatic)
.map((e) => _$staticMethod(ctx, element, e))
.join('\n');
}

String _$staticMethod(
BindgenContext ctx, ClassElement element, MethodElement method) {
return '''
/// Wrapper for the [${element.name}.${method.name}] method
static \$Value? \$${method.name}(Runtime runtime, \$Value? target, List<\$Value?> args) {
final value = ${element.name}.${method.name}(
${argumentAccessors(method.parameters)}
);
return ${wrapVar(ctx, method.returnType, "value")};
}
''';
}

String $staticGetters(BindgenContext ctx, ClassElement element) {
return element.accessors
.where((e) => e.isStatic && e.isGetter)
.map((e) => _$staticGetter(ctx, element, e))
.join('\n');
}

String _$staticGetter(
BindgenContext ctx, ClassElement element, PropertyAccessorElement getter) {
return '''
/// Wrapper for the [${element.name}.${getter.name}] getter
static \$Value? \$${getter.name}(Runtime runtime, \$Value? target, List<\$Value?> args) {
final value = ${element.name}.${getter.name};
return ${wrapVar(ctx, getter.returnType, "value")};
}
''';
}
23 changes: 22 additions & 1 deletion lib/src/eval/bindgen/type.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:dart_eval/src/eval/bindgen/context.dart';
Expand Down Expand Up @@ -74,7 +75,15 @@ String? wrapVar(BindgenContext ctx, DartType type, String expr) {
return '\$null()';
}

final wrapped = wrapType(ctx, type, expr) ?? '\$Object($expr)';
var wrapped = wrapType(ctx, type, expr);

if (wrapped == null) {
if (ctx.unknownTypes.add(type.element!.name!)) {
print('Warning: type ${type.element!.name} is not bound, '
'falling back to \$Object');
}
wrapped = '\$Object($expr)';
}

if (type.nullabilitySuffix == NullabilitySuffix.question) {
return '$expr == null ? \$null() : $wrapped';
Expand Down Expand Up @@ -105,6 +114,18 @@ String? wrapType(BindgenContext ctx, DartType type, String expr) {
if (defaultCstr.contains(name)) {
return '\$$name($expr)';
}
if (name == 'List') {
final generic = type as ParameterizedType;
final arg = generic.typeArguments.first;
return '\$List.view($expr, (e) => ${wrapVar(ctx, arg, 'e')})';
}
return '\$$name.wrap($expr)';
}

final typeEl = type.element;
if (typeEl is ClassElement &&
typeEl.metadata.any((e) => e.element?.displayName == 'Bind')) {
ctx.imports.add(typeEl.library.source.uri.toString());
return '\$$name.wrap($expr)';
}

Expand Down
23 changes: 20 additions & 3 deletions lib/src/eval/compiler/collection/set_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,37 @@ Variable compileSetOrMapLiteral(SetOrMapLiteral l, CompilerContext ctx) {
keyResultTypes.addAll(_result.second.map((e) => e.first));
valueResultTypes.addAll(_result.second.map((e) => e.second));
}
var isEmpty = false;
if (_collection == null) {
isEmpty = true;
if (specifiedValueType != null ||
(specifiedKeyType == null && specifiedValueType == null)) {
// make an empty Map
ctx.pushOp(PushMap.make(), PushMap.LEN);
_collection = Variable.alloc(
ctx,
CoreTypes.map.ref(ctx).copyWith(specifiedTypeArgs: [
specifiedKeyType ?? CoreTypes.dynamic.ref(ctx),
specifiedValueType ?? CoreTypes.dynamic.ref(ctx),
], boxed: false));
} else {
throw CompileError('Sets are not currently supported');
}
}
ctx.endAllocScope(popAdjust: -1);
ctx.scopeFrameOffset++;
ctx.allocNest.last++;

if (specifiedValueType == null) {
if (specifiedValueType == null && !isEmpty) {
return Variable(
_collection!.scopeFrameOffset,
_collection.scopeFrameOffset,
_collection.type.copyWith(boxed: false, specifiedTypeArgs: [
TypeRef.commonBaseType(ctx, keyResultTypes.toSet()),
TypeRef.commonBaseType(ctx, valueResultTypes.toSet())
]));
}

return _collection!;
return _collection;
}

Pair<Variable, List<Pair<TypeRef, TypeRef>>> compileSetOrMapElement(
Expand Down
4 changes: 2 additions & 2 deletions lib/src/eval/compiler/helpers/tearoff.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ extension TearOff on Variable {
.map((rt) => rt.toJson())
.toList();

final named =
((parameters?.parameters ?? []).where((element) => element.isNamed));
final named = ((parameters?.parameters ?? <FormalParameter>[])
.where((element) => element.isNamed));
final sortedNamedArgs = named.toList()
..sort((e1, e2) => (e1.name!.lexeme).compareTo((e2.name!.lexeme)));
final sortedNamedArgNames =
Expand Down
Loading

0 comments on commit 3da8fe2

Please sign in to comment.