diff --git a/lib/dart_eval.dart b/lib/dart_eval.dart index bf00b64..031d384 100644 --- a/lib/dart_eval.dart +++ b/lib/dart_eval.dart @@ -1,5 +1,5 @@ /// A library providing a Dart bytecode compiler and interpreter. -library dart_eval; +library; export 'src/eval/eval.dart'; export 'src/eval/runtime/runtime.dart' show Runtime; diff --git a/lib/dart_eval_bridge.dart b/lib/dart_eval_bridge.dart index 7d50234..a6af0f0 100644 --- a/lib/dart_eval_bridge.dart +++ b/lib/dart_eval_bridge.dart @@ -1,4 +1,4 @@ -library dart_eval.bridge; +library; export 'package:pub_semver/pub_semver.dart' show Version; export 'src/eval/runtime/runtime.dart' show Runtime; diff --git a/lib/dart_eval_security.dart b/lib/dart_eval_security.dart index 4456465..364e866 100644 --- a/lib/dart_eval_security.dart +++ b/lib/dart_eval_security.dart @@ -1,4 +1,4 @@ -library dart_eval.security; +library; export 'src/eval/runtime/security/permission.dart'; export 'src/eval/runtime/security/permissions/filesystem.dart'; diff --git a/lib/src/eval/bindgen/bindgen.dart b/lib/src/eval/bindgen/bindgen.dart index e853ed0..1ce6036 100644 --- a/lib/src/eval/bindgen/bindgen.dart +++ b/lib/src/eval/bindgen/bindgen.dart @@ -155,15 +155,21 @@ class Bindgen implements BridgeDeclarationRegistry { final units = analysisResult.unit.declarations; - final resolved = units - .where((declaration) => declaration.declaredFragment != null) - .map((declaration) => declaration is ClassDeclaration - ? _$instance(ctx, declaration.declaredFragment!.element) - : (declaration is EnumDeclaration - ? _$enum(ctx, declaration.declaredFragment!.element) - : null)) - .toList() - .nonNulls; + final Iterable resolved; + try { + resolved = units + .where((declaration) => declaration.declaredFragment != null) + .map((declaration) => declaration is ClassDeclaration + ? _$instance(ctx, declaration.declaredFragment!.element) + : (declaration is EnumDeclaration + ? _$enum(ctx, declaration.declaredFragment!.element) + : null)) + .toList() + .nonNulls; + } on Error { + print('Failed to resolve $filePath:'); + rethrow; + } if (resolved.isEmpty) { return null; diff --git a/lib/src/eval/bindgen/bridge_declaration.dart b/lib/src/eval/bindgen/bridge_declaration.dart index e336dc6..474de0d 100644 --- a/lib/src/eval/bindgen/bridge_declaration.dart +++ b/lib/src/eval/bindgen/bridge_declaration.dart @@ -96,6 +96,7 @@ ${fields(ctx, element)} String constructors(BindgenContext ctx, InterfaceElement2 element) { return element.constructors2 + .where((e) => !e.isPrivate) .map((e) => bridgeConstructorDef(ctx, constructor: e)) .join('\n'); } @@ -111,6 +112,7 @@ String methods(BindgenContext ctx, InterfaceElement2 element) { return methods.values .where( (m) => !(const ['==', 'toString', 'noSuchMethod'].contains(m.name3))) + .where((m) => !m.isPrivate) .map((m) => bridgeMethodDef(ctx, method: m)) .join('\n'); } @@ -126,6 +128,7 @@ String getters(BindgenContext ctx, InterfaceElement2 element) { return getters.values .where((m) => !(const ['hashCode', 'runtimeType'].contains(m.name3))) + .where((element) => !element.isPrivate) .where((element) => !element.isSynthetic || (element is EnumElement2 && @@ -145,7 +148,7 @@ String setters(BindgenContext ctx, InterfaceElement2 element) { }; return setters.values - .where((element) => !element.isSynthetic) + .where((element) => !element.isSynthetic && !element.isPrivate) .map((e) => bridgeSetterDef(ctx, setter: e)) .join('\n'); } @@ -161,8 +164,8 @@ String fields(BindgenContext ctx, InterfaceElement2 element) { for (final f in element.fields2) f.name3: f }; - final fields = allFields.values - .where((element) => !element.isSynthetic && !element.isEnumConstant); + final fields = allFields.values.where((element) => + !element.isSynthetic && !element.isEnumConstant && !element.isPrivate); return fields .map( diff --git a/lib/src/eval/bindgen/methods.dart b/lib/src/eval/bindgen/methods.dart index 13bb122..6acf022 100644 --- a/lib/src/eval/bindgen/methods.dart +++ b/lib/src/eval/bindgen/methods.dart @@ -1,6 +1,7 @@ import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:dart_eval/src/eval/bindgen/context.dart'; +import 'package:dart_eval/src/eval/bindgen/operator.dart'; import 'package:dart_eval/src/eval/bindgen/parameters.dart'; import 'package:dart_eval/src/eval/bindgen/permission.dart'; import 'package:dart_eval/src/eval/bindgen/type.dart'; @@ -20,12 +21,13 @@ String $methods(BindgenContext ctx, InterfaceElement2 element) { .map((e) { final returnsValue = e.returnType is! VoidType && !e.returnType.isDartCoreNull; + final op = resolveMethodOperator(e.displayName); return ''' - static const \$Function __${e.displayName} = \$Function(_${e.displayName}); - static \$Value? _${e.displayName}(Runtime runtime, \$Value? target, List<\$Value?> args) { + static const \$Function __${op.name} = \$Function(_${op.name}); + static \$Value? _${op.name}(Runtime runtime, \$Value? target, List<\$Value?> args) { ${assertMethodPermissions(e)} final self = target! as \$${element.name3}; - ${returnsValue ? 'final result = ' : ''}self.\$value.${e.displayName}(${argumentAccessors(ctx, e.formalParameters)}); + ${returnsValue ? 'final result = ' : ''}${op.format('self.\$value', argumentAccessors(ctx, e.formalParameters))}; return ${wrapVar(ctx, e.returnType, 'result')}; }'''; }).join('\n'); diff --git a/lib/src/eval/bindgen/operator.dart b/lib/src/eval/bindgen/operator.dart new file mode 100644 index 0000000..0249aac --- /dev/null +++ b/lib/src/eval/bindgen/operator.dart @@ -0,0 +1,95 @@ +abstract class OperatorMethod { + String get name; + String format(String obj, List args); +} + +class FunctionOperator implements OperatorMethod { + @override + final String name; + + const FunctionOperator(this.name); + + @override + String format(String obj, List args) { + return '$obj.$name(${args.join(', ')})'; + } +} + +class BinaryOperator implements OperatorMethod { + final String op; + + @override + final String name; + + const BinaryOperator(this.op, this.name); + + @override + String format(String obj, List args) { + return '($obj $op ${args[0]})'; + } +} + +class UnaryOperator implements OperatorMethod { + final String op; + + @override + final String name; + + const UnaryOperator(this.op, this.name); + + @override + String format(String obj, List args) { + return '$op$obj'; + } +} + +class IndexGetOperator implements OperatorMethod { + @override + final String name; + + const IndexGetOperator(this.name); + + @override + String format(String obj, List args) { + return '$obj[${args[0]}]'; + } +} + +class IndexSetOperator implements OperatorMethod { + @override + final String name; + + const IndexSetOperator(this.name); + + @override + String format(String obj, List args) { + return '$obj[${args[0]}] = ${args[1]}'; + } +} + +OperatorMethod resolveMethodOperator(String name) => + kOperatorNames[name] ?? FunctionOperator(name); + +// https://dart.dev/language/methods#operators +final kOperatorNames = { + '<': BinaryOperator('<', 'operatorLt'), + '>': BinaryOperator('>', 'operatorGt'), + '<=': BinaryOperator('<=', 'operatorLte'), + '>=': BinaryOperator('>=', 'operatorGte'), + '==': BinaryOperator('==', 'operatorEq'), + '~': UnaryOperator('~', 'operatorBitNot'), + '-': BinaryOperator('-', 'operatorMinus'), + '+': BinaryOperator('+', 'operatorPlus'), + '/': BinaryOperator('/', 'operatorDiv'), + '~/': BinaryOperator('~/', 'operatorIntDiv'), + '*': BinaryOperator('*', 'operatorMul'), + '%': BinaryOperator('%', 'operatorMod'), + '|': BinaryOperator('|', 'operatorBitOr'), + '^': BinaryOperator('^', 'operatorBitXor'), + '&': BinaryOperator('&', 'operatorBitAnd'), + '<<': BinaryOperator('<<', 'operatorShl'), + '>>': BinaryOperator('>>', 'operatorShr'), + '>>>': BinaryOperator('>>>', 'operatorUshr'), + '[]=': IndexSetOperator('operatorIndexSet'), + '[]': IndexGetOperator('operatorIndexGet'), +}; diff --git a/lib/src/eval/bindgen/parameters.dart b/lib/src/eval/bindgen/parameters.dart index c2d6f6a..b7d6110 100644 --- a/lib/src/eval/bindgen/parameters.dart +++ b/lib/src/eval/bindgen/parameters.dart @@ -1,5 +1,6 @@ import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/type.dart'; +import 'package:collection/collection.dart'; import 'package:dart_eval/src/eval/bindgen/bridge.dart'; import 'package:dart_eval/src/eval/bindgen/context.dart'; import 'package:dart_eval/src/eval/bindgen/type.dart'; @@ -39,79 +40,81 @@ String _parameterFrom(BindgenContext ctx, FormalParameterElement parameter) { '''; } -String argumentAccessors( - BindgenContext ctx, List params, +String argumentAccessor( + BindgenContext ctx, int index, FormalParameterElement param, {Map paramMapping = const {}, bool isBridgeMethod = false}) { final paramBuffer = StringBuffer(); - for (var i = 0; i < params.length; i++) { - final idx = i + (isBridgeMethod ? 1 : 0); - final param = params[i]; - if (param.isNamed) { - paramBuffer.write('${paramMapping[param.name3] ?? param.name3}: '); + final idx = index + (isBridgeMethod ? 1 : 0); + if (param.isNamed) { + paramBuffer.write('${paramMapping[param.name3] ?? param.name3}: '); + } + final type = param.type; + if (type.isDartCoreFunction || type is FunctionType) { + paramBuffer.write('('); + if (type is FunctionType) { + paramBuffer.write(parameterHeader(type.formalParameters)); } - final type = param.type; - if (type.isDartCoreFunction || type is FunctionType) { - paramBuffer.write('('); - if (type is FunctionType) { - paramBuffer.write(parameterHeader(type.formalParameters)); + paramBuffer.write(') {\n'); + if (type is FunctionType) { + if (type.returnType is! VoidType) { + paramBuffer.write('return '); } - paramBuffer.write(') {\n'); - if (type is FunctionType) { - if (type.returnType is! VoidType) { - paramBuffer.write('return '); - } - } - final q = (param.isRequired ? '' : '?'); - final call = (param.isRequired ? '' : '?.call'); - paramBuffer - .write('(args[$idx]! as EvalCallable$q)$call(runtime, null, ['); - if (type is FunctionType) { - for (var j = 0; j < type.formalParameters.length; j++) { - final ftParam = type.formalParameters[j]; - final name = ftParam.name3 == null || ftParam.name3!.isEmpty - ? 'arg$j' - : ftParam.name3!; - paramBuffer - .write(wrapVar(ctx, ftParam.type, name, forCollection: true)); - if (j < type.formalParameters.length - 1) { - paramBuffer.write(', '); - } + } + final q = (param.isRequired ? '' : '?'); + final call = (param.isRequired ? '' : '?.call'); + paramBuffer.write('(args[$idx]! as EvalCallable$q)$call(runtime, null, ['); + if (type is FunctionType) { + for (var j = 0; j < type.formalParameters.length; j++) { + final ftParam = type.formalParameters[j]; + final name = ftParam.name3 == null || ftParam.name3!.isEmpty + ? 'arg$j' + : ftParam.name3!; + paramBuffer + .write(wrapVar(ctx, ftParam.type, name, forCollection: true)); + if (j < type.formalParameters.length - 1) { + paramBuffer.write(', '); } } - paramBuffer.write('])'); - if (type is FunctionType) { - if (type.returnType is! VoidType) { - paramBuffer.write('?.\$value'); - } + } + paramBuffer.write('])'); + if (type is FunctionType) { + if (type.returnType is! VoidType) { + paramBuffer.write('?.\$value'); } - paramBuffer.write(';\n}'); + } + paramBuffer.write(';\n}'); + } else { + final needsCast = + type.isDartCoreList || type.isDartCoreMap || type.isDartCoreSet; + if (needsCast) { + paramBuffer.write('('); + } + paramBuffer.write('args[$idx]'); + final accessor = needsCast ? 'reified' : 'value'; + if (param.isRequired) { + paramBuffer.write('!.\$$accessor'); } else { - final needsCast = - type.isDartCoreList || type.isDartCoreMap || type.isDartCoreSet; - if (needsCast) { - paramBuffer.write('('); - } - paramBuffer.write('args[$idx]'); - final accessor = needsCast ? 'reified' : 'value'; - if (param.isRequired) { - paramBuffer.write('!.\$$accessor'); - } else { - paramBuffer.write('?.\$$accessor'); - if (param.hasDefaultValue) { - paramBuffer.write(' ?? ${param.defaultValueCode}'); - } - } - if (needsCast) { - final q = (param.isRequired ? '' : '?'); - paramBuffer.write(' as ${type.element3!.name3}$q'); - paramBuffer.write(')$q.cast()'); + paramBuffer.write('?.\$$accessor'); + if (param.hasDefaultValue) { + paramBuffer.write(' ?? ${param.defaultValueCode}'); } } - - if (i < params.length - 1) { - paramBuffer.write(', '); + if (needsCast) { + final q = (param.isRequired ? '' : '?'); + paramBuffer.write(' as ${type.element3!.name3}$q'); + paramBuffer.write(')$q.cast()'); } } return paramBuffer.toString(); } + +List argumentAccessors( + BindgenContext ctx, List params, + {Map paramMapping = const {}, + bool isBridgeMethod = false}) { + return params + .mapIndexed((i, p) => argumentAccessor(ctx, i, p, + paramMapping: paramMapping, isBridgeMethod: isBridgeMethod)) + .toList(); +} diff --git a/lib/src/eval/bindgen/properties.dart b/lib/src/eval/bindgen/properties.dart index ae19ccc..02ed671 100644 --- a/lib/src/eval/bindgen/properties.dart +++ b/lib/src/eval/bindgen/properties.dart @@ -1,6 +1,7 @@ import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:dart_eval/src/eval/bindgen/context.dart'; +import 'package:dart_eval/src/eval/bindgen/operator.dart'; import 'package:dart_eval/src/eval/bindgen/parameters.dart'; import 'package:dart_eval/src/eval/bindgen/permission.dart'; import 'package:dart_eval/src/eval/bindgen/type.dart'; @@ -52,11 +53,12 @@ String propertyGetters(BindgenContext ctx, InterfaceElement2 element, ''').join('\n')}${methods0.map((e) { final returnsValue = e.returnType is! VoidType && !e.returnType.isDartCoreNull; + final op = resolveMethodOperator(e.displayName); return ''' case '${e.displayName}': return \$Function((runtime, target, args) { ${assertMethodPermissions(e)} - ${returnsValue ? 'final result = ' : ''}super.${e.displayName}(${argumentAccessors(ctx, e.formalParameters, isBridgeMethod: true)}); + ${returnsValue ? 'final result = ' : ''}${op.format('super', argumentAccessors(ctx, e.formalParameters, isBridgeMethod: true))}; return ${wrapVar(ctx, e.returnType, 'result')}; });'''; }).join('\n')}\n}'; @@ -67,7 +69,7 @@ String propertyGetters(BindgenContext ctx, InterfaceElement2 element, return ${wrapVar(ctx, e.type.returnType, '_${e.name3}', metadata: e.metadata2.annotations)}; ''').join('\n')}${methods0.map((e) => ''' case '${e.name3}': - return __${e.name3}; + return __${resolveMethodOperator(e.name3!).name}; ''').join('\n')}\n}'; } diff --git a/lib/src/eval/bindgen/statics.dart b/lib/src/eval/bindgen/statics.dart index cca4fd2..d9e0c6c 100644 --- a/lib/src/eval/bindgen/statics.dart +++ b/lib/src/eval/bindgen/statics.dart @@ -47,7 +47,7 @@ String _$constructor( static \$Value? \$$name(Runtime runtime, \$Value? thisValue, List<\$Value?> args) { return ${!isBridge ? '\$${element.name3}.wrap(' : ''} $fullyQualifiedConstructorId( - ${argumentAccessors(ctx, constructor.formalParameters)} + ${argumentAccessors(ctx, constructor.formalParameters).join(', ')} ${!isBridge ? '),' : ''} ); } @@ -68,7 +68,7 @@ String _$staticMethod( static \$Value? \$${method.name3}(Runtime runtime, \$Value? target, List<\$Value?> args) { ${assertMethodPermissions(method)} final value = ${element.name3}.${method.name3}( - ${argumentAccessors(ctx, method.formalParameters)} + ${argumentAccessors(ctx, method.formalParameters).join(', ')} ); return ${wrapVar(ctx, method.returnType, "value")}; } diff --git a/lib/src/eval/cli/bind.dart b/lib/src/eval/cli/bind.dart index 94d9479..e2ffaf2 100644 --- a/lib/src/eval/cli/bind.dart +++ b/lib/src/eval/cli/bind.dart @@ -6,8 +6,10 @@ import 'package:dart_eval/dart_eval_bridge.dart'; import 'package:dart_eval/src/eval/bindgen/bindgen.dart'; import 'package:dart_eval/src/eval/cli/utils.dart'; import 'package:dart_style/dart_style.dart'; +import 'package:glob/glob.dart'; import 'package:path/path.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; +import 'package:yaml/yaml.dart' show loadYaml, YamlList; const defaultImports = ''' // ignore_for_file: unused_import, unnecessary_import @@ -78,6 +80,9 @@ void cliBind( var singleResult = ''; var numBound = 0; + final analyzePath = join(projectRoot.path, 'analysis_options.yaml'); + final excludes = readAnalyzerExcludes(File(analyzePath)); + Future bindLoop(String pkg, Directory dir, String root) async { if (!dir.existsSync()) return; for (final file in dir.listSync()) { @@ -86,6 +91,7 @@ void cliBind( filename.endsWith('.dart') && !filename.endsWith('.eval.dart')) { final p = relative(file.path, from: root).replaceAll('\\', '/'); + if (excludes.any((e) => e.matches(p))) continue; final uri = 'package:${posix.join(packageName, p)}'; final output = await bindgen.parse(file, filename, uri, all); if (output != null) { @@ -161,3 +167,16 @@ class ${packageName.toPascalCase()}Plugin implements EvalPlugin { print('Created bindings for $numBound files.'); } } + +List readAnalyzerExcludes(File path) { + if (!path.existsSync()) return []; + final doc = loadYaml(path.readAsStringSync()); + final excludes = (doc['analyzer'] ?? {})['exclude']; + if (excludes == null || excludes is! YamlList) return []; + final reLib = RegExp(r'^lib/'); + return excludes + .whereType() + .where((value) => value.startsWith('lib/')) + .map((value) => Glob(value.replaceFirst(reLib, ''))) + .toList(); +} diff --git a/lib/src/eval/compiler/builtins.dart b/lib/src/eval/compiler/builtins.dart index 5b53da7..0a29643 100644 --- a/lib/src/eval/compiler/builtins.dart +++ b/lib/src/eval/compiler/builtins.dart @@ -96,7 +96,7 @@ class KnownMethodArg { Map>? _knownMethods; -Map> getKnownMethods(ctx) { +Map> getKnownMethods(CompilerContext ctx) { if (_knownMethods != null) { return _knownMethods!; } diff --git a/lib/src/eval/compiler/collection/for.dart b/lib/src/eval/compiler/collection/for.dart index 5f8e05d..71350ef 100644 --- a/lib/src/eval/compiler/collection/for.dart +++ b/lib/src/eval/compiler/collection/for.dart @@ -69,32 +69,31 @@ List compileForElementForList( update: (ctx) => loopVariable.setValue(ctx, iterator.getProperty(ctx, 'current')), updateBeforeBody: true); - } - - parts as ForParts; - - macroLoop(ctx, null, - initialization: (ctx) { - if (parts is ForPartsWithDeclarations) { - compileVariableDeclarationList(parts.variables, ctx); - } else if (parts is ForPartsWithExpression) { - if (parts.initialization != null) { - compileExpressionAndDiscardResult(parts.initialization!, ctx); + } else if (parts is ForParts) { + macroLoop(ctx, null, + initialization: (ctx) { + if (parts is ForPartsWithDeclarations) { + compileVariableDeclarationList(parts.variables, ctx); + } else if (parts is ForPartsWithExpression) { + if (parts.initialization != null) { + compileExpressionAndDiscardResult(parts.initialization!, ctx); + } + } + }, + condition: parts.condition == null + ? null + : (ctx) => compileExpression(parts.condition!, ctx), + body: (ctx, ert) { + potentialReturnTypes + .addAll(compileListElement(e.body, list, ctx, box)); + return StatementInfo(-1); + }, + update: (ctx) { + for (final u in parts.updaters) { + compileExpressionAndDiscardResult(u, ctx); } - } - }, - condition: parts.condition == null - ? null - : (ctx) => compileExpression(parts.condition!, ctx), - body: (ctx, ert) { - potentialReturnTypes.addAll(compileListElement(e.body, list, ctx, box)); - return StatementInfo(-1); - }, - update: (ctx) { - for (final u in parts.updaters) { - compileExpressionAndDiscardResult(u, ctx); - } - }); + }); + } return potentialReturnTypes; } diff --git a/lib/src/eval/compiler/statement/statement.dart b/lib/src/eval/compiler/statement/statement.dart index cc7deae..193fda1 100644 --- a/lib/src/eval/compiler/statement/statement.dart +++ b/lib/src/eval/compiler/statement/statement.dart @@ -20,39 +20,43 @@ import 'block.dart'; StatementInfo compileStatement( Statement s, AlwaysReturnType? expectedReturnType, CompilerContext ctx) { - if (s is Block) { - return compileBlock(s, expectedReturnType, ctx); - } else if (s is VariableDeclarationStatement) { - return compileVariableDeclarationStatement(s, ctx); - } else if (s is ExpressionStatement) { - final V = compileExpressionAndDiscardResult(s.expression, ctx); - if (V != null && V.type == CoreTypes.never.ref(ctx)) { - return StatementInfo(-1, willAlwaysThrow: true); + try { + if (s is Block) { + return compileBlock(s, expectedReturnType, ctx); + } else if (s is VariableDeclarationStatement) { + return compileVariableDeclarationStatement(s, ctx); + } else if (s is ExpressionStatement) { + final V = compileExpressionAndDiscardResult(s.expression, ctx); + if (V != null && V.type == CoreTypes.never.ref(ctx)) { + return StatementInfo(-1, willAlwaysThrow: true); + } + return StatementInfo(-1); + } else if (s is ReturnStatement) { + return compileReturn(ctx, s, expectedReturnType); + } else if (s is ForStatement) { + return compileForStatement(s, ctx, expectedReturnType); + } else if (s is WhileStatement) { + return compileWhileStatement(s, ctx, expectedReturnType); + } else if (s is DoStatement) { + return compileDoStatement(s, ctx, expectedReturnType); + } else if (s is IfStatement) { + return compileIfStatement(s, ctx, expectedReturnType); + } else if (s is SwitchStatement) { + return compileSwitchStatement(s, ctx, expectedReturnType); + } else if (s is TryStatement) { + return compileTryStatement(s, ctx, expectedReturnType); + } else if (s is AssertStatement) { + return compileAssertStatement(s, ctx, expectedReturnType); + } else if (s is BreakStatement) { + return compileBreakStatement(s, ctx); + } else if (s is PatternVariableDeclarationStatement) { + return compilePatternVariableDeclarationStatement(s, ctx); } - return StatementInfo(-1); - } else if (s is ReturnStatement) { - return compileReturn(ctx, s, expectedReturnType); - } else if (s is ForStatement) { - return compileForStatement(s, ctx, expectedReturnType); - } else if (s is WhileStatement) { - return compileWhileStatement(s, ctx, expectedReturnType); - } else if (s is DoStatement) { - return compileDoStatement(s, ctx, expectedReturnType); - } else if (s is IfStatement) { - return compileIfStatement(s, ctx, expectedReturnType); - } else if (s is SwitchStatement) { - return compileSwitchStatement(s, ctx, expectedReturnType); - } else if (s is TryStatement) { - return compileTryStatement(s, ctx, expectedReturnType); - } else if (s is AssertStatement) { - return compileAssertStatement(s, ctx, expectedReturnType); - } else if (s is BreakStatement) { - return compileBreakStatement(s, ctx); - } else if (s is PatternVariableDeclarationStatement) { - return compilePatternVariableDeclarationStatement(s, ctx); - } else { - throw CompileError('Unknown statement type ${s.runtimeType}'); + } on Error { + print('Failed to compile a statement "$s"'); + rethrow; } + throw CompileError('Unknown statement type ${s.runtimeType}'); } class StatementInfo { diff --git a/lib/src/eval/compiler/type.dart b/lib/src/eval/compiler/type.dart index 24745b7..72e65e9 100644 --- a/lib/src/eval/compiler/type.dart +++ b/lib/src/eval/compiler/type.dart @@ -245,8 +245,11 @@ class TypeRef { } final lib = ctx.libraryMap[spec.library] ?? (throw CompileError('Bridge: cannot find library ${spec.library}')); - return ctx.visibleTypes[lib]![spec.name]! - .copyWith(specifiedTypeArgs: specifiedTypeArgs, boxed: true); + final typeSpec = ctx.visibleTypes[lib]![spec.name] ?? + (throw CompileError( + 'Bridge: cannot find type ${spec.name} in library ${spec.library}')); + return typeSpec.copyWith( + specifiedTypeArgs: specifiedTypeArgs, boxed: true); } final ref = typeReference.ref; if (ref != null) { diff --git a/lib/src/eval/shared/stdlib/core/object.dart b/lib/src/eval/shared/stdlib/core/object.dart index 56e6b47..2116b49 100644 --- a/lib/src/eval/shared/stdlib/core/object.dart +++ b/lib/src/eval/shared/stdlib/core/object.dart @@ -135,7 +135,7 @@ class $Object implements $Instance { return $int($value.hashCode); } - throw UnimplementedError(); + throw UnimplementedError("\$Object.$identifier"); } /// dart_eval implementation of [Object.hash] @@ -284,7 +284,7 @@ class $Object implements $Instance { @override void $setProperty(Runtime runtime, String identifier, $Value value) { - throw UnimplementedError(); + throw UnimplementedError("set \$Object.$identifier"); } @override diff --git a/lib/src/eval/shared/stdlib/core/regexp.dart b/lib/src/eval/shared/stdlib/core/regexp.dart index 22608c9..081cab4 100644 --- a/lib/src/eval/shared/stdlib/core/regexp.dart +++ b/lib/src/eval/shared/stdlib/core/regexp.dart @@ -147,7 +147,7 @@ class $RegExp implements $Instance { int $getRuntimeType(Runtime runtime) => runtime.lookupType($type.spec!); } -class $RegExpMatch implements $Instance, RegExpMatch { +class $RegExpMatch implements $Instance, Match { /// Compile-time type reference to [RegExpMatch] static const $type = BridgeTypeRef(BridgeTypeSpec('dart:core', 'RegExpMatch')); @@ -232,7 +232,6 @@ class $RegExpMatch implements $Instance, RegExpMatch { @override int get groupCount => $value.groupCount; - @override Iterable get groupNames => $value.groupNames; @override @@ -241,7 +240,6 @@ class $RegExpMatch implements $Instance, RegExpMatch { @override String get input => $value.input; - @override String? namedGroup(String name) => $value.namedGroup(name); @override diff --git a/lib/src/eval/shared/stdlib/core/uri.dart b/lib/src/eval/shared/stdlib/core/uri.dart index a2815cb..5589cd2 100644 --- a/lib/src/eval/shared/stdlib/core/uri.dart +++ b/lib/src/eval/shared/stdlib/core/uri.dart @@ -139,146 +139,99 @@ class $Uri implements $Instance { ]), isStatic: true), 'dataFromBytes': BridgeMethodDef( - BridgeFunctionDef( - returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - params: [ - BridgeParameter( - 'bytes', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.list, [ - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int)) - ])), - false), - ], - namedParams: [ - BridgeParameter( - 'mimeType', + BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [ + BridgeParameter( + 'bytes', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.list, + [BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int))])), + false), + ], namedParams: [ + BridgeParameter('mimeType', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), true), + BridgeParameter( + 'parameters', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.map, [ BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - true), - BridgeParameter( - 'parameters', - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.map, [ - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)) - ]), - ), - true), - BridgeParameter( - 'percentEncoded', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool)), - true), - ]), + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)) + ]), + ), + true), + BridgeParameter('percentEncoded', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool)), true), + ]), isStatic: true), 'dataFromString': BridgeMethodDef( - BridgeFunctionDef( - returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - params: [ - BridgeParameter( - 'content', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - false), - ], - namedParams: [ - BridgeParameter( - 'mimeType', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - true), - BridgeParameter( - 'parameters', - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.map, [ - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.string)), - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.string)) - ]), - nullable: true), - true), - BridgeParameter( - 'base64', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool)), - true), - ]), + BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [ + BridgeParameter('content', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false), + ], namedParams: [ + BridgeParameter('mimeType', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), true), + BridgeParameter( + 'parameters', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.map, [ + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)) + ]), + nullable: true), + true), + BridgeParameter('base64', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool)), true), + ]), isStatic: true), 'directory': BridgeMethodDef( - BridgeFunctionDef( - returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - params: [ - BridgeParameter( - 'path', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - false), - ], - namedParams: [ - BridgeParameter('windows', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool)), true) - ]), + BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [ + BridgeParameter('path', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false), + ], namedParams: [ + BridgeParameter('windows', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool)), true) + ]), isStatic: true), 'file': BridgeMethodDef( - BridgeFunctionDef( - returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - params: [ - BridgeParameter( - 'path', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - false), - ], - namedParams: [ - BridgeParameter('windows', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool)), true) - ]), + BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [ + BridgeParameter('path', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false), + ], namedParams: [ + BridgeParameter('windows', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool)), true) + ]), isStatic: true), 'http': BridgeMethodDef( - BridgeFunctionDef( - returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - params: [ - BridgeParameter( - 'authority', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - false), - BridgeParameter( - 'unencodedPath', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - true), - BridgeParameter( - 'queryParameters', - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.map, [ - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.string)), - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.dynamic)) - ]), - nullable: true), - true), - ], - namedParams: []), + BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [ + BridgeParameter('authority', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false), + BridgeParameter('unencodedPath', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), true), + BridgeParameter( + 'queryParameters', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.map, [ + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.dynamic)) + ]), + nullable: true), + true), + ], namedParams: []), isStatic: true), 'https': BridgeMethodDef( - BridgeFunctionDef( - returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - params: [ - BridgeParameter( - 'authority', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - false), - BridgeParameter( - 'unencodedPath', - BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), - true), - BridgeParameter( - 'queryParameters', - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.map, [ - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.string)), - BridgeTypeAnnotation( - BridgeTypeRef(CoreTypes.dynamic)) - ]), - nullable: true), - true), - ], - namedParams: []), + BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [ + BridgeParameter('authority', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false), + BridgeParameter('unencodedPath', + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), true), + BridgeParameter( + 'queryParameters', + BridgeTypeAnnotation( + BridgeTypeRef(CoreTypes.map, [ + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), + BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.dynamic)) + ]), + nullable: true), + true), + ], namedParams: []), isStatic: true), 'parseIPv4Address': BridgeMethodDef( BridgeFunctionDef( diff --git a/lib/stdlib/async.dart b/lib/stdlib/async.dart index 07f277e..fc5c153 100644 --- a/lib/stdlib/async.dart +++ b/lib/stdlib/async.dart @@ -1,5 +1,5 @@ /// Provides dart:async bridge classes and wrappers -library dart_eval.stdlib.async; +library; export '../src/eval/shared/stdlib/async/future.dart'; export '../src/eval/shared/stdlib/async/stream_controller.dart'; diff --git a/lib/stdlib/collection.dart b/lib/stdlib/collection.dart index 50d5f1d..def15ec 100644 --- a/lib/stdlib/collection.dart +++ b/lib/stdlib/collection.dart @@ -1,5 +1,5 @@ /// Provides dart:collection bridge classes and wrappers -library dart_eval.stdlib.collection; +library; export '../src/eval/shared/stdlib/collection/linked_hash_map.dart'; export '../src/eval/shared/stdlib/collection/list_queue.dart'; diff --git a/lib/stdlib/convert.dart b/lib/stdlib/convert.dart index c11f243..6da9b8b 100644 --- a/lib/stdlib/convert.dart +++ b/lib/stdlib/convert.dart @@ -1,5 +1,5 @@ /// Provides dart:convert bridge classes and wrappers -library dart_eval.stdlib.convert; +library; export '../src/eval/shared/stdlib/convert/codec.dart'; export '../src/eval/shared/stdlib/convert/converter.dart'; diff --git a/lib/stdlib/core.dart b/lib/stdlib/core.dart index c51d70b..b454de1 100644 --- a/lib/stdlib/core.dart +++ b/lib/stdlib/core.dart @@ -1,5 +1,5 @@ /// Provides dart:core bridge classes and wrappers -library dart_eval.stdlib.core; +library; export '../src/eval/shared/stdlib/core/base.dart'; export '../src/eval/shared/stdlib/core/collection.dart'; diff --git a/lib/stdlib/io.dart b/lib/stdlib/io.dart index 9981513..5645d15 100644 --- a/lib/stdlib/io.dart +++ b/lib/stdlib/io.dart @@ -1,5 +1,5 @@ /// Provides dart:io bridge classes and wrappers -library dart_eval.stdlib.io; +library; export '../src/eval/shared/stdlib/io/directory.dart'; export '../src/eval/shared/stdlib/io/file_system_entity.dart'; diff --git a/lib/stdlib/math.dart b/lib/stdlib/math.dart index d43c8ea..8021065 100644 --- a/lib/stdlib/math.dart +++ b/lib/stdlib/math.dart @@ -1,4 +1,4 @@ /// Provides dart:math bridge classes and wrappers -library dart_eval.stdlib.math; +library; export '../src/eval/shared/stdlib/math/point.dart'; diff --git a/lib/stdlib/typed_data.dart b/lib/stdlib/typed_data.dart index 1ff38f7..c85afa4 100644 --- a/lib/stdlib/typed_data.dart +++ b/lib/stdlib/typed_data.dart @@ -1,4 +1,4 @@ /// Provides dart:typed_data bridge classes and wrappers -library dart_eval.stdlib.typed_data; +library; export '../src/eval/shared/stdlib/typed_data/typed_data.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index b96ff4e..745b597 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: analyzer: ^7.0.0 collection: ^1.18.0 directed_graph: '>=0.3.9 <0.5.0' + glob: ^2.1.3 json_annotation: ^4.7.0 pubspec_parse: ^1.2.0 path: ^1.8.0 @@ -34,6 +35,7 @@ dependencies: package_config: ^2.1.0 dart_style: ^3.0.0 change_case: ^2.1.0 + yaml: ^3.1.3 dev_dependencies: test: ^1.25.0 diff --git a/test/bridge_test.dart b/test/bridge_test.dart index 3747c35..678ee26 100644 --- a/test/bridge_test.dart +++ b/test/bridge_test.dart @@ -23,7 +23,7 @@ void main() { 'example': { 'main.dart': ''' import 'package:bridge_lib/bridge_lib.dart'; - + bool main() { final test = TestClass(4); return test.runTest(5, b: 'hi'); @@ -48,16 +48,16 @@ void main() { 'example': { 'main.dart': ''' import 'package:bridge_lib/bridge_lib.dart'; - + class MyTestClass extends TestClass { MyTestClass(int someNumber) : super(someNumber); - + @override bool runTest(int a, {String b = 'wow'}) { return super.runTest(a + 2 + someNumber, b: b); } } - + bool main() { final test = MyTestClass(18); return test.runTest(5, b: 'cool'); @@ -75,6 +75,43 @@ void main() { expect(runtime.executeLib('package:example/main.dart', 'main'), true); }); + test('Changing a field in the constructor of a subclassed bridge class', () { + compiler.defineBridgeClasses([$TestClass.$declaration]); + + final program = compiler.compile({ + 'example': { + 'main.dart': ''' + import 'package:bridge_lib/bridge_lib.dart'; + + class MyTestClass extends TestClass { + MyTestClass(int someNumber) : super(someNumber) { + someNumber = 4; + } + + @override + bool runTest(int a, {String b = 'wow'}) { + return super.runTest(a + 2, b: b); + } + } + + bool main() { + final test = MyTestClass(1); + // 1 + 2 + (someNumber == 4) = 7 > b.length + return test.runTest(1, b: '123456'); + } + ''' + } + }); + + final runtime = Runtime.ofProgram(program); + + runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart', + 'TestClass.', $TestClass.$construct, + isBridge: true); + + expect(runtime.executeLib('package:example/main.dart', 'main'), true); + }); + test('Using a subclassed bridge class outside the runtime', () { compiler.defineBridgeClasses([$TestClass.$declaration]); @@ -82,16 +119,16 @@ void main() { 'example': { 'main.dart': ''' import 'package:bridge_lib/bridge_lib.dart'; - + class MyTestClass extends TestClass { MyTestClass(int someNumber) : super(someNumber); - + @override bool runTest(int a, {String b = 'wow'}) { return super.runTest(a + 2, b: b); } } - + TestClass main() { final test = MyTestClass(0, b: 'hello'); return test; @@ -120,7 +157,7 @@ void main() { 'example': { 'main.dart': ''' import 'package:bridge_lib/bridge_lib.dart'; - + bool main() { return TestClass.runStaticTest('Okay'); } @@ -145,7 +182,7 @@ void main() { 'example': { 'main.dart': ''' import 'package:bridge_lib/bridge_lib.dart'; - + TestEnum main() { final map = { 'one': TestEnum.one, @@ -228,7 +265,7 @@ void main() { 'example': { 'main.dart': ''' import 'dart:async'; - + void main(Function callback) async { callback('a'); await callback('w'); diff --git a/test/convert_test.dart b/test/convert_test.dart index d152bd5..5acab27 100644 --- a/test/convert_test.dart +++ b/test/convert_test.dart @@ -1,5 +1,5 @@ @TestOn('vm') -library convert_test; +library; import 'package:dart_eval/dart_eval.dart'; import 'package:dart_eval/dart_eval_bridge.dart'; diff --git a/test/functional1_test.dart b/test/functional1_test.dart index a942dd0..05750f9 100644 --- a/test/functional1_test.dart +++ b/test/functional1_test.dart @@ -275,7 +275,7 @@ void main() { 'package:extensions_test/main.dart', 'main', ); - expect((value as RegExpMatch).group(0), '1'); + expect((value as Match).group(0), '1'); }); test('Bridged enum equality ternary assignment', () { diff --git a/test/io_test.dart b/test/io_test.dart index c358225..c781f8c 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -1,5 +1,5 @@ @TestOn('vm') -library io_test; +library; import 'package:dart_eval/dart_eval.dart'; import 'package:dart_eval/dart_eval_security.dart'; diff --git a/test/operator_test.dart b/test/operator_test.dart new file mode 100644 index 0000000..cc43860 --- /dev/null +++ b/test/operator_test.dart @@ -0,0 +1,90 @@ +import 'package:dart_eval/dart_eval.dart'; +import 'package:dart_eval/stdlib/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('Operator method tests', () { + late Compiler compiler; + + setUp(() { + compiler = Compiler(); + }); + + test('Operator ==', () { + final runtime = compiler.compileWriteAndLoad({ + 'operator_test': { + 'main.dart': ''' + class MyClass { + final int value; + + MyClass(this.value); + + @override + bool operator==(Object other) => other is MyClass && other.value == value; + } + + List main() { + final cls = MyClass(1); + return [ + cls == MyClass(2), + cls == null, + cls == MyClass(1), + cls == cls, + ]; + } + ''' + } + }); + + expect(runtime.executeLib('package:operator_test/main.dart', 'main'), [ + $bool(false), $bool(false), $bool(true), $bool(true), + ]); + }, skip: true); + + test('Operator has object context', () { + final runtime = compiler.compileWriteAndLoad({ + 'operator_test': { + 'main.dart': ''' + class MyClass { + final int value = 1; + int operator+(int add) => value + add; + } + int main() => MyClass() + 1; + ''' + } + }); + + expect(runtime.executeLib('package:operator_test/main.dart', 'main'), 4); + }, skip: true); + + test('Operator []', () { + final runtime = compiler.compileWriteAndLoad({ + 'operator_test': { + 'main.dart': ''' + class MyClass { + final value = [1, 2]; + + MyClass(); + + int operator[](int index) => value[index]; + + void operator[]=(int index, int value) { + this.value[index] = value; + } + } + + List main() { + final cls = MyClass(); + cls[0] = 3; + return [cls[0], cls[1]]; + } + ''' + } + }); + + expect(runtime.executeLib('package:operator_test/main.dart', 'main'), [ + $int(1), $int(2), + ]); + }, skip: true); + }); +} \ No newline at end of file diff --git a/test/util/current_dir_overrides.dart b/test/util/current_dir_overrides.dart index 36dda4e..70951e7 100644 --- a/test/util/current_dir_overrides.dart +++ b/test/util/current_dir_overrides.dart @@ -21,7 +21,7 @@ import 'package:path/path.dart' as p; /// CurrentDirIOOverrides('/my/custom/path'), /// ); /// ``` -class CurrentDirIOOverrides extends IOOverrides { +base class CurrentDirIOOverrides extends IOOverrides { final String currentDir; CurrentDirIOOverrides(this.currentDir);