diff --git a/lib/src/eval/bindgen/bindgen.dart b/lib/src/eval/bindgen/bindgen.dart index 80bc12f..df6181e 100644 --- a/lib/src/eval/bindgen/bindgen.dart +++ b/lib/src/eval/bindgen/bindgen.dart @@ -7,7 +7,9 @@ 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/errors.dart'; import 'package:dart_eval/src/eval/bindgen/methods.dart'; +import 'package:dart_eval/src/eval/bindgen/properties.dart'; import 'package:dart_eval/src/eval/bindgen/statics.dart'; import 'package:dart_eval/src/eval/bindgen/type.dart'; import 'dart:io' as io; @@ -104,8 +106,11 @@ class Bindgen { if (bindAnno == null) { return null; } - final override = - bindAnno.computeConstantValue()?.getField('overrideLibrary'); + final bindAnnoValue = bindAnno.computeConstantValue(); + final implicitSupers = + bindAnnoValue?.getField('implicitSupers')?.toBoolValue() ?? false; + ctx.implicitSupers = implicitSupers; + final override = bindAnnoValue?.getField('overrideLibrary'); if (override != null && !override.isNull) { final overrideUri = override.toStringValue(); if (overrideUri != null) { @@ -141,9 +146,26 @@ ${$setProperty(ctx, element)} String $superclassWrapper(BindgenContext ctx, ClassElement element) { final supertype = element.supertype; final objectWrapper = '\$Object(\$value)'; - return supertype == null - ? objectWrapper - : wrapType(ctx, supertype, '\$value') ?? objectWrapper; + if (supertype == null || ctx.implicitSupers) { + ctx.imports.add('package:dart_eval/stdlib/core.dart'); + return objectWrapper; + } + final narrowWrapper = wrapType(ctx, supertype, '\$value'); + if (narrowWrapper == null) { + print('Warning: Could not wrap supertype $supertype of ${element.name},' + ' falling back to \$Object. Add a @Bind annotation to $supertype' + ' or set `implicitSupers: true`'); + ctx.imports.add('package:dart_eval/stdlib/core.dart'); + return objectWrapper; + } + return narrowWrapper; + } + + String $getRuntimeType(ClassElement element) { + return ''' + @override + int \$getRuntimeType(Runtime runtime) => runtime.lookupType(\$spec); +'''; } String $wrap(BindgenContext ctx, ClassElement element) { @@ -160,61 +182,4 @@ ${$setProperty(ctx, element)} \$${element.name}.wrap(this.\$value) : _superclass = ${$superclassWrapper(ctx, element)}; '''; } - - String $getProperty(BindgenContext ctx, ClassElement element) { - return ''' - @override - \$Value? \$getProperty(Runtime runtime, String identifier) { - ${propertyGetters(ctx, element)} - return _superclass.\$getProperty(runtime, identifier); - } -'''; - } - - String propertyGetters(BindgenContext ctx, ClassElement element) { - final _getters = element.accessors.where((accessor) => - accessor.isGetter && !accessor.isStatic && !accessor.isPrivate); - final _methods = element.methods.where((method) => !method.isStatic); - if (_getters.isEmpty && _methods.isEmpty) { - return ''; - } - return 'switch (identifier) {\n' + _getters.map((e) => ''' - case '${e.displayName}': - final _${e.displayName} = \$value.${e.displayName}; - return ${wrapVar(ctx, e.type.returnType, '_${e.displayName}', metadata: e.nonSynthetic.metadata)}; - ''').join('\n') + _methods.map((e) => ''' - case '${e.displayName}': - return __${e.displayName}; - ''').join('\n') + '\n' + '}'; - } - - String $getRuntimeType(ClassElement element) { - return ''' - @override - int \$getRuntimeType(Runtime runtime) => runtime.lookupType(\$type.spec!); -'''; - } - - String $setProperty(BindgenContext ctx, ClassElement element) { - return ''' - @override - void \$setProperty(Runtime runtime, String identifier, \$Value value) { - ${propertySetters(ctx, element)} - return _superclass.\$setProperty(runtime, identifier, value); - } -'''; - } - - String propertySetters(BindgenContext ctx, ClassElement element) { - final _setters = element.accessors.where((element) => - element.isSetter && !element.isStatic && !element.isPrivate); - if (_setters.isEmpty) { - return ''; - } - return 'switch (identifier) {\n' + _setters.map((e) => ''' - case '${e.displayName}': - \$value.${e.displayName} = value.\$value; - return; - ''').join('\n') + '\n' + '}'; - } } diff --git a/lib/src/eval/bindgen/bridge_declaration.dart b/lib/src/eval/bindgen/bridge_declaration.dart index b9a1f0c..5880c34 100644 --- a/lib/src/eval/bindgen/bridge_declaration.dart +++ b/lib/src/eval/bindgen/bridge_declaration.dart @@ -8,7 +8,7 @@ String bindTypeSpec(BindgenContext ctx, ClassElement element) { return ''' static const \$spec = BridgeTypeSpec( '${uri}', - '${element.name}', + '${element.name.replaceAll(r'$', r'\$')}', ); '''; } @@ -29,7 +29,7 @@ String? bindBridgeDeclaration(BindgenContext ctx, ClassElement element) { if (typeParams.isNotEmpty) { genericsStr = '''\ngenerics: { ${typeParams.map((e) { - final boundStr = e.bound != null + final boundStr = e.bound != null && !ctx.implicitSupers ? '\$extends: ${bridgeTypeRefFromType(ctx, e.bound!)}' : ''; return '\'${e.name}\': BridgeGenericParam($boundStr)'; @@ -85,40 +85,65 @@ String constructors(BindgenContext ctx, ClassElement element) { } String methods(BindgenContext ctx, ClassElement element) { - return element.methods.map((e) => bridgeMethodDef(ctx, method: e)).join('\n'); + final methods = { + if (ctx.implicitSupers) + for (var s in element.allSupertypes) + for (final m in s.element.methods.where((m) => !m.isStatic)) m.name: m, + for (final m in element.methods) m.name: m + }; + return methods.values + .where( + (m) => !(const ['==', 'toString', 'noSuchMethod'].contains(m.name))) + .map((m) => bridgeMethodDef(ctx, method: m)) + .join('\n'); } String getters(BindgenContext ctx, ClassElement element) { - var getters = element.accessors - .where((element) => element.isGetter && !element.isSynthetic) - .map((element) => element.displayName) - .toList(); - - return getters - .map((e) => bridgeGetterDef(ctx, getter: element.getGetter(e)!)) - .join('\n'); + final accessors = { + if (ctx.implicitSupers) + for (var s in element.allSupertypes) + for (final a in s.element.accessors.where((a) => !a.isStatic)) + a.name: a, + for (final a in element.accessors) a.name: a + }; + + var getters = accessors.values + .where((m) => !(const ['hashCode', 'runtimeType'].contains(m.name))) + .where((element) => element.isGetter && !element.isSynthetic); + + return getters.map((e) => bridgeGetterDef(ctx, getter: e)).join('\n'); } String setters(BindgenContext ctx, ClassElement element) { - var setters = element.accessors - .where((element) => element.isSetter && !element.isSynthetic) - .map((element) => element.displayName) - .toList(); - - return setters - .map((e) => bridgeSetterDef(ctx, setter: element.getSetter(e)!)) - .join('\n'); + final accessors = { + if (ctx.implicitSupers) + for (var s in element.allSupertypes) + for (final a in s.element.accessors.where((a) => !a.isStatic)) + a.name: a, + for (final a in element.accessors) a.name: a + }; + + var setters = accessors.values + .where((element) => element.isSetter && !element.isSynthetic); + + return setters.map((e) => bridgeSetterDef(ctx, setter: e)).join('\n'); } String fields(BindgenContext ctx, ClassElement element) { - var fields = element.fields - .where((element) => !element.isSynthetic) - .map((element) => element.displayName) - .toList(); + final allFields = { + if (ctx.implicitSupers) + for (var s in element.allSupertypes) + if (s is ClassElement) + for (final f in (s as ClassElement).fields.where((f) => !f.isStatic)) + f.name: f, + for (final f in element.fields) f.name: f + }; + + final fields = allFields.values.where((element) => !element.isSynthetic); return fields .map( - (e) => bridgeFieldDef(ctx, field: element.getField(e)!), + (e) => bridgeFieldDef(ctx, field: e), ) .join('\n'); } diff --git a/lib/src/eval/bindgen/context.dart b/lib/src/eval/bindgen/context.dart index abd4466..5b4ba57 100644 --- a/lib/src/eval/bindgen/context.dart +++ b/lib/src/eval/bindgen/context.dart @@ -6,6 +6,7 @@ class BindgenContext { final bool wrap; final bool all; final Map libOverrides = {}; + bool implicitSupers = false; BindgenContext(this.uri, {required this.wrap, required this.all}); } diff --git a/lib/src/eval/bindgen/methods.dart b/lib/src/eval/bindgen/methods.dart index 9b508f0..c2c1127 100644 --- a/lib/src/eval/bindgen/methods.dart +++ b/lib/src/eval/bindgen/methods.dart @@ -6,8 +6,17 @@ import 'package:dart_eval/src/eval/bindgen/permission.dart'; import 'package:dart_eval/src/eval/bindgen/type.dart'; String $methods(BindgenContext ctx, ClassElement element) { - return element.methods + final methods = { + if (ctx.implicitSupers) + for (var s in element.allSupertypes) + for (final m in s.element.methods) m.name: m, + for (final m in element.methods) m.name: m + }; + + return methods.values .where((method) => !method.isPrivate && !method.isStatic) + .where( + (m) => !(const ['==', 'toString', 'noSuchMethod'].contains(m.name))) .map((e) { final returnsValue = e.returnType is! VoidType && !e.returnType.isDartCoreNull; diff --git a/lib/src/eval/bindgen/properties.dart b/lib/src/eval/bindgen/properties.dart new file mode 100644 index 0000000..399c37a --- /dev/null +++ b/lib/src/eval/bindgen/properties.dart @@ -0,0 +1,77 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:dart_eval/src/eval/bindgen/context.dart'; +import 'package:dart_eval/src/eval/bindgen/type.dart'; + +String $getProperty(BindgenContext ctx, ClassElement element) { + return ''' + @override + \$Value? \$getProperty(Runtime runtime, String identifier) { + ${propertyGetters(ctx, element)} + return _superclass.\$getProperty(runtime, identifier); + } +'''; +} + +String propertyGetters(BindgenContext ctx, ClassElement element) { + final accessors = { + if (ctx.implicitSupers) + for (var s in element.allSupertypes) + for (final a in s.element.accessors) a.name: a, + for (final a in element.accessors) a.name: a + }; + final methods = { + if (ctx.implicitSupers) + for (var s in element.allSupertypes) + for (final m in s.element.methods) m.name: m, + for (final m in element.methods) m.name: m + }; + final _getters = accessors.values + .where((accessor) => + accessor.isGetter && !accessor.isStatic && !accessor.isPrivate) + .where((a) => !(const ['hashCode', 'runtimeType'].contains(a.name))); + + final _methods = methods.values + .where((method) => !method.isPrivate && !method.isStatic) + .where( + (m) => !(const ['==', 'toString', 'noSuchMethod'].contains(m.name))); + if (_getters.isEmpty && _methods.isEmpty) { + return ''; + } + return 'switch (identifier) {\n' + _getters.map((e) => ''' + case '${e.displayName}': + final _${e.displayName} = \$value.${e.displayName}; + return ${wrapVar(ctx, e.type.returnType, '_${e.displayName}', metadata: e.nonSynthetic.metadata)}; + ''').join('\n') + _methods.map((e) => ''' + case '${e.displayName}': + return __${e.displayName}; + ''').join('\n') + '\n' + '}'; +} + +String $setProperty(BindgenContext ctx, ClassElement element) { + return ''' + @override + void \$setProperty(Runtime runtime, String identifier, \$Value value) { + ${propertySetters(ctx, element)} + return _superclass.\$setProperty(runtime, identifier, value); + } +'''; +} + +String propertySetters(BindgenContext ctx, ClassElement element) { + final accessors = { + if (ctx.implicitSupers) + for (var s in element.allSupertypes) + for (final a in s.element.accessors) a.name: a, + for (final a in element.accessors) a.name: a + }; + final _setters = accessors.values.where( + (element) => element.isSetter && !element.isStatic && !element.isPrivate); + if (_setters.isEmpty) { + return ''; + } + return 'switch (identifier) {\n' + _setters.map((e) => ''' + case '${e.displayName}': + \$value.${e.displayName} = value.\$value; + return; + ''').join('\n') + '\n' + '}'; +} diff --git a/lib/src/eval/bindgen/statics.dart b/lib/src/eval/bindgen/statics.dart index d91c2d3..c0841f5 100644 --- a/lib/src/eval/bindgen/statics.dart +++ b/lib/src/eval/bindgen/statics.dart @@ -6,7 +6,7 @@ import 'package:dart_eval/src/eval/bindgen/type.dart'; String $constructors(BindgenContext ctx, ClassElement element) { return element.constructors - .where((cstr) => !cstr.isPrivate && !element.isAbstract) + .where((cstr) => !cstr.isPrivate) .map((e) => _$constructor(ctx, element, e)) .join('\n'); } diff --git a/lib/src/eval/bindgen/type.dart b/lib/src/eval/bindgen/type.dart index 9847444..58640ad 100644 --- a/lib/src/eval/bindgen/type.dart +++ b/lib/src/eval/bindgen/type.dart @@ -39,7 +39,7 @@ String bridgeTypeSpecFrom(BindgenContext ctx, DartType type) { final element = type.element!; final lib = element.library!; final uri = ctx.libOverrides[element.name] ?? lib.source.uri.toString(); - return 'BridgeTypeSpec(\'${uri}\', \'${element.name}\')'; + return 'BridgeTypeSpec(\'${uri}\', \'${element.name!.replaceAll(r'$', r'\$')}\')'; } String? builtinTypeFrom(DartType type) {