| // Copyright (c) 2019, 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. |
| library kernel.text_serializer_from_kernel_nodes_test; |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/text/serializer_combinators.dart'; |
| import 'package:kernel/text/text_reader.dart'; |
| import 'package:kernel/text/text_serializer.dart'; |
| |
| void main() { |
| initializeSerializers(); |
| test(); |
| } |
| |
| abstract class TestCase<T extends Node> { |
| final String name; |
| final T node; |
| final SerializationState serializationState; |
| final DeserializationState deserializationState; |
| final String expectation; |
| |
| TestCase._( |
| {this.name, |
| this.node, |
| this.expectation, |
| SerializationState serializationState, |
| DeserializationState deserializationState}) |
| : this.serializationState = |
| serializationState ?? new SerializationState(null), |
| this.deserializationState = deserializationState ?? |
| new DeserializationState(null, new CanonicalName.root()); |
| |
| T readNode(String input, DeserializationState state); |
| |
| String writeNode(T node, SerializationState state); |
| } |
| |
| class StatementTestCase extends TestCase<Statement> { |
| StatementTestCase( |
| {String name, |
| Statement node, |
| String expectation, |
| SerializationState serializationState, |
| DeserializationState deserializationState}) |
| : super._( |
| name: name, |
| node: node, |
| expectation: expectation, |
| serializationState: serializationState, |
| deserializationState: deserializationState); |
| |
| Statement readNode(String input, DeserializationState state) { |
| TextIterator stream = new TextIterator(input, 0); |
| stream.moveNext(); |
| Statement result = statementSerializer.readFrom(stream, state); |
| if (stream.moveNext()) { |
| throw new StateError("Found extra tokens at the end of the statement."); |
| } |
| return result; |
| } |
| |
| String writeNode(Statement statement, SerializationState state) { |
| StringBuffer buffer = new StringBuffer(); |
| statementSerializer.writeTo(buffer, statement, state); |
| return buffer.toString(); |
| } |
| } |
| |
| class ProcedureTestCase extends TestCase<Procedure> { |
| ProcedureTestCase( |
| {String name, |
| Procedure node, |
| String expectation, |
| SerializationState serializationState, |
| DeserializationState deserializationState}) |
| : super._( |
| name: name, |
| node: node, |
| expectation: expectation, |
| serializationState: serializationState, |
| deserializationState: deserializationState); |
| |
| Procedure readNode(String input, DeserializationState state) { |
| TextIterator stream = new TextIterator(input, 0); |
| stream.moveNext(); |
| Procedure result = procedureSerializer.readFrom(stream, state); |
| if (stream.moveNext()) { |
| throw new StateError("Found extra tokens at the end of the procedure."); |
| } |
| return result; |
| } |
| |
| String writeNode(Procedure procedure, SerializationState state) { |
| StringBuffer buffer = new StringBuffer(); |
| procedureSerializer.writeTo(buffer, procedure, state); |
| return buffer.toString(); |
| } |
| } |
| |
| void test() { |
| List<String> failures = []; |
| List<TestCase> tests = <TestCase>[ |
| new StatementTestCase( |
| name: 'let dynamic x = 42 in x;', |
| node: () { |
| VariableDeclaration x = new VariableDeclaration('x', |
| type: const DynamicType(), initializer: new IntLiteral(42)); |
| return new ExpressionStatement(new Let(x, new VariableGet(x))); |
| }(), |
| expectation: '' |
| '(expr (let (var "x^0" (dynamic) (int 42) ())' |
| ' (get-var "x^0" _)))'), |
| new StatementTestCase( |
| name: 'let dynamic x = 42 in let Bottom x^0 = null in x;', |
| node: () { |
| VariableDeclaration outterLetVar = new VariableDeclaration('x', |
| type: const DynamicType(), initializer: new IntLiteral(42)); |
| VariableDeclaration innerLetVar = new VariableDeclaration('x', |
| type: const BottomType(), initializer: new NullLiteral()); |
| return new ExpressionStatement(new Let(outterLetVar, |
| new Let(innerLetVar, new VariableGet(outterLetVar)))); |
| }(), |
| expectation: '' |
| '(expr (let (var "x^0" (dynamic) (int 42) ())' |
| ' (let (var "x^1" (bottom) (null) ())' |
| ' (get-var "x^0" _))))'), |
| new StatementTestCase( |
| name: 'let dynamic x = 42 in let Bottom x^0 = null in x^0;', |
| node: () { |
| VariableDeclaration outterLetVar = new VariableDeclaration('x', |
| type: const DynamicType(), initializer: new IntLiteral(42)); |
| VariableDeclaration innerLetVar = new VariableDeclaration('x', |
| type: const BottomType(), initializer: new NullLiteral()); |
| return new ExpressionStatement(new Let(outterLetVar, |
| new Let(innerLetVar, new VariableGet(innerLetVar)))); |
| }(), |
| expectation: '' |
| '(expr (let (var "x^0" (dynamic) (int 42) ())' |
| ' (let (var "x^1" (bottom) (null) ())' |
| ' (get-var "x^1" _))))'), |
| () { |
| VariableDeclaration x = |
| new VariableDeclaration('x', type: const DynamicType()); |
| return new StatementTestCase( |
| name: '/* suppose: dynamic x; */ x = 42;', |
| node: new ExpressionStatement(new VariableSet(x, new IntLiteral(42))), |
| expectation: '(expr (set-var "x^0" (int 42)))', |
| serializationState: new SerializationState( |
| new SerializationEnvironment(null) |
| ..addBinder(x, 'x^0') |
| ..close(), |
| ), |
| deserializationState: new DeserializationState( |
| new DeserializationEnvironment(null) |
| ..addBinder('x^0', x) |
| ..close(), |
| new CanonicalName.root())); |
| }(), |
| () { |
| Field field = new Field(new Name('field'), type: const DynamicType()); |
| Library library = new Library( |
| new Uri(scheme: 'package', path: 'foo/bar.dart'), |
| fields: <Field>[field]); |
| Component component = new Component(libraries: <Library>[library]); |
| component.computeCanonicalNames(); |
| return new StatementTestCase( |
| name: '/* suppose top-level: dynamic field; */ field;', |
| node: new ExpressionStatement(new StaticGet(field)), |
| expectation: '' |
| '(expr (get-static "package:foo/bar.dart::@fields::field"))', |
| serializationState: new SerializationState(null), |
| deserializationState: new DeserializationState(null, component.root)); |
| }(), |
| () { |
| Field field = new Field(new Name('field'), type: const DynamicType()); |
| Library library = new Library( |
| new Uri(scheme: 'package', path: 'foo/bar.dart'), |
| fields: <Field>[field]); |
| Component component = new Component(libraries: <Library>[library]); |
| component.computeCanonicalNames(); |
| return new StatementTestCase( |
| name: '/* suppose top-level: dynamic field; */ field = 1;', |
| node: |
| new ExpressionStatement(new StaticSet(field, new IntLiteral(1))), |
| expectation: '' |
| '(expr' |
| ' (set-static "package:foo/bar.dart::@fields::field" (int 1)))', |
| serializationState: new SerializationState(null), |
| deserializationState: new DeserializationState(null, component.root)); |
| }(), |
| () { |
| Procedure topLevelProcedure = new Procedure( |
| new Name('foo'), |
| ProcedureKind.Method, |
| new FunctionNode(null, positionalParameters: <VariableDeclaration>[ |
| new VariableDeclaration('x', type: const DynamicType()) |
| ]), |
| isStatic: true); |
| Library library = new Library( |
| new Uri(scheme: 'package', path: 'foo/bar.dart'), |
| procedures: <Procedure>[topLevelProcedure]); |
| Component component = new Component(libraries: <Library>[library]); |
| component.computeCanonicalNames(); |
| return new StatementTestCase( |
| name: '/* suppose top-level: foo(dynamic x) {...}; */ foo(42);', |
| node: new ExpressionStatement(new StaticInvocation.byReference( |
| topLevelProcedure.reference, |
| new Arguments(<Expression>[new IntLiteral(42)]), |
| isConst: false)), |
| expectation: '' |
| '(expr (invoke-static "package:foo/bar.dart::@methods::foo"' |
| ' () ((int 42)) ()))', |
| serializationState: new SerializationState(null), |
| deserializationState: new DeserializationState(null, component.root)); |
| }(), |
| () { |
| Procedure factoryConstructor = new Procedure( |
| new Name('foo'), ProcedureKind.Factory, new FunctionNode(null), |
| isStatic: true, isConst: true); |
| Class klass = |
| new Class(name: 'A', procedures: <Procedure>[factoryConstructor]); |
| Library library = new Library( |
| new Uri(scheme: 'package', path: 'foo/bar.dart'), |
| classes: <Class>[klass]); |
| Component component = new Component(libraries: <Library>[library]); |
| component.computeCanonicalNames(); |
| return new StatementTestCase( |
| name: '' |
| '/* suppose A { const A(); const factory A.foo() = A; } */' |
| ' const A.foo();', |
| node: new ExpressionStatement(new StaticInvocation.byReference( |
| factoryConstructor.reference, new Arguments([]), |
| isConst: true)), |
| expectation: '' |
| '(expr (invoke-const-static' |
| ' "package:foo/bar.dart::A::@factories::foo"' |
| ' () () ()))', |
| serializationState: new SerializationState(null), |
| deserializationState: new DeserializationState(null, component.root)); |
| }(), |
| () { |
| Field field = new Field(new Name('field'), type: const DynamicType()); |
| Class klass = new Class(name: 'A', fields: <Field>[field]); |
| Library library = new Library( |
| new Uri(scheme: 'package', path: 'foo/bar.dart'), |
| classes: <Class>[klass]); |
| Component component = new Component(libraries: <Library>[library]); |
| component.computeCanonicalNames(); |
| |
| VariableDeclaration x = |
| new VariableDeclaration('x', type: const DynamicType()); |
| return new StatementTestCase( |
| name: '/* suppose A {dynamic field;} A x; */ x.{A::field};', |
| node: new ExpressionStatement(new DirectPropertyGet.byReference( |
| new VariableGet(x), field.reference)), |
| expectation: '' |
| '(expr (get-direct-prop (get-var "x^0" _)' |
| ' "package:foo/bar.dart::A::@fields::field"))', |
| serializationState: |
| new SerializationState(new SerializationEnvironment(null) |
| ..addBinder(x, 'x^0') |
| ..close()), |
| deserializationState: new DeserializationState( |
| new DeserializationEnvironment(null) |
| ..addBinder('x^0', x) |
| ..close(), |
| component.root)); |
| }(), |
| () { |
| Field field = new Field(new Name('field'), type: const DynamicType()); |
| Class klass = new Class(name: 'A', fields: <Field>[field]); |
| Library library = new Library( |
| new Uri(scheme: 'package', path: 'foo/bar.dart'), |
| classes: <Class>[klass]); |
| Component component = new Component(libraries: <Library>[library]); |
| component.computeCanonicalNames(); |
| |
| VariableDeclaration x = |
| new VariableDeclaration('x', type: const DynamicType()); |
| return new StatementTestCase( |
| name: '/* suppose A {dynamic field;} A x; */ x.{A::field} = 42;', |
| node: new ExpressionStatement(new DirectPropertySet.byReference( |
| new VariableGet(x), field.reference, new IntLiteral(42))), |
| expectation: '' |
| '(expr (set-direct-prop (get-var "x^0" _)' |
| ' "package:foo/bar.dart::A::@fields::field" (int 42)))', |
| serializationState: |
| new SerializationState(new SerializationEnvironment(null) |
| ..addBinder(x, 'x^0') |
| ..close()), |
| deserializationState: new DeserializationState( |
| new DeserializationEnvironment(null) |
| ..addBinder('x^0', x) |
| ..close(), |
| component.root)); |
| }(), |
| () { |
| Procedure method = new Procedure( |
| new Name('foo'), ProcedureKind.Method, new FunctionNode(null), |
| isStatic: true, isConst: true); |
| Class klass = new Class(name: 'A', procedures: <Procedure>[method]); |
| Library library = new Library( |
| new Uri(scheme: 'package', path: 'foo/bar.dart'), |
| classes: <Class>[klass]); |
| Component component = new Component(libraries: <Library>[library]); |
| component.computeCanonicalNames(); |
| |
| VariableDeclaration x = |
| new VariableDeclaration('x', type: const DynamicType()); |
| return new StatementTestCase( |
| name: '/* suppose A {foo() {...}} A x; */ x.{A::foo}();', |
| node: new ExpressionStatement(new DirectMethodInvocation.byReference( |
| new VariableGet(x), method.reference, new Arguments([]))), |
| expectation: '' |
| '(expr (invoke-direct-method (get-var "x^0" _)' |
| ' "package:foo/bar.dart::A::@methods::foo"' |
| ' () () ()))', |
| serializationState: |
| new SerializationState(new SerializationEnvironment(null) |
| ..addBinder(x, 'x^0') |
| ..close()), |
| deserializationState: new DeserializationState( |
| new DeserializationEnvironment(null) |
| ..addBinder('x^0', x) |
| ..close(), |
| component.root)); |
| }(), |
| () { |
| Constructor constructor = |
| new Constructor(new FunctionNode(null), name: new Name('foo')); |
| Class klass = |
| new Class(name: 'A', constructors: <Constructor>[constructor]); |
| Library library = new Library( |
| new Uri(scheme: 'package', path: 'foo/bar.dart'), |
| classes: <Class>[klass]); |
| Component component = new Component(libraries: <Library>[library]); |
| component.computeCanonicalNames(); |
| return new StatementTestCase( |
| name: '/* suppose A {A.foo();} */ new A();', |
| node: new ExpressionStatement(new ConstructorInvocation.byReference( |
| constructor.reference, new Arguments([]))), |
| expectation: '' |
| '(expr (invoke-constructor' |
| ' "package:foo/bar.dart::A::@constructors::foo"' |
| ' () () ()))', |
| serializationState: new SerializationState(null), |
| deserializationState: new DeserializationState(null, component.root)); |
| }(), |
| () { |
| Constructor constructor = new Constructor(new FunctionNode(null), |
| name: new Name('foo'), isConst: true); |
| Class klass = |
| new Class(name: 'A', constructors: <Constructor>[constructor]); |
| Library library = new Library( |
| new Uri(scheme: 'package', path: 'foo/bar.dart'), |
| classes: <Class>[klass]); |
| Component component = new Component(libraries: <Library>[library]); |
| component.computeCanonicalNames(); |
| return new StatementTestCase( |
| name: '/* suppose A {const A.foo();} */ const A();', |
| node: new ExpressionStatement(new ConstructorInvocation.byReference( |
| constructor.reference, new Arguments([]), |
| isConst: true)), |
| expectation: '' |
| '(expr (invoke-const-constructor' |
| ' "package:foo/bar.dart::A::@constructors::foo"' |
| ' () () ()))', |
| serializationState: new SerializationState(null), |
| deserializationState: new DeserializationState(null, component.root)); |
| }(), |
| () { |
| TypeParameter outterParam = |
| new TypeParameter('T', const DynamicType(), const DynamicType()); |
| TypeParameter innerParam = |
| new TypeParameter('T', const DynamicType(), const DynamicType()); |
| return new StatementTestCase( |
| name: '/* T Function<T>(T Function<T>()); */', |
| node: new ExpressionStatement(new TypeLiteral(new FunctionType( |
| [ |
| new FunctionType( |
| [], |
| new TypeParameterType(innerParam, Nullability.legacy), |
| Nullability.legacy, |
| typeParameters: [innerParam]) |
| ], |
| new TypeParameterType(outterParam, Nullability.legacy), |
| Nullability.legacy, |
| typeParameters: [outterParam]))), |
| expectation: '' |
| '(expr (type (-> ("T^0") ((dynamic)) ((dynamic)) ' |
| '((-> ("T^1") ((dynamic)) ((dynamic)) () () () ' |
| '(par "T^1" _))) () () (par "T^0" _))))', |
| serializationState: |
| new SerializationState(new SerializationEnvironment(null)), |
| deserializationState: new DeserializationState( |
| new DeserializationEnvironment(null), null)); |
| }(), |
| () { |
| TypeParameter t = |
| new TypeParameter('T', const DynamicType(), const DynamicType()); |
| VariableDeclaration t1 = new VariableDeclaration('t1', |
| type: new TypeParameterType(t, Nullability.legacy)); |
| VariableDeclaration t2 = new VariableDeclaration('t2', |
| type: new TypeParameterType(t, Nullability.legacy)); |
| return new StatementTestCase( |
| name: '/* <T>(T t1, [T t2]) => t1; */', |
| node: new ExpressionStatement(new FunctionExpression(new FunctionNode( |
| new ReturnStatement(new VariableGet(t1)), |
| typeParameters: [t], |
| positionalParameters: [t1, t2], |
| requiredParameterCount: 1, |
| namedParameters: [], |
| returnType: new TypeParameterType(t, Nullability.legacy), |
| asyncMarker: AsyncMarker.Sync))), |
| expectation: '' |
| '(expr (fun (sync ("T^0") ((dynamic)) ((dynamic)) ((var ' |
| '"t1^1" (par "T^0" _) _ ())) ((var "t2^2" (par "T^0" _) ' |
| '_ ())) () (par "T^0" _) (ret (get-var "t1^1" _)))))', |
| serializationState: |
| new SerializationState(new SerializationEnvironment(null)), |
| deserializationState: new DeserializationState( |
| new DeserializationEnvironment(null), null)); |
| }(), |
| () { |
| VariableDeclaration x = VariableDeclaration('x', type: DynamicType()); |
| Procedure foo = Procedure( |
| Name('foo'), |
| ProcedureKind.Method, |
| FunctionNode(ReturnStatement(VariableGet(x)), |
| positionalParameters: [x]), |
| isStatic: true); |
| Library library = Library(Uri(scheme: 'package', path: 'foo/bar.dart'), |
| procedures: [foo]); |
| Component component = Component(libraries: [library]); |
| component.computeCanonicalNames(); |
| return new ProcedureTestCase( |
| name: 'foo(x) => x;', |
| node: foo, |
| expectation: '' |
| '(static-method (public "foo")' |
| ' (sync () () () ((var "x^0" (dynamic) _ ())) () () (dynamic) (ret (get-var "x^0" _))))', |
| serializationState: |
| new SerializationState(new SerializationEnvironment(null)), |
| deserializationState: new DeserializationState( |
| new DeserializationEnvironment(null), null)); |
| }(), |
| ]; |
| for (TestCase testCase in tests) { |
| String roundTripInput = |
| testCase.writeNode(testCase.node, testCase.serializationState); |
| if (roundTripInput != testCase.expectation) { |
| failures.add('' |
| '* initial serialization for test "${testCase.name}"' |
| ' gave output "${roundTripInput}"'); |
| } |
| |
| TreeNode deserialized = |
| testCase.readNode(roundTripInput, testCase.deserializationState); |
| String roundTripOutput = |
| testCase.writeNode(deserialized, testCase.serializationState); |
| if (roundTripOutput != roundTripInput) { |
| failures.add('' |
| '* input "${testCase.name}" gave output "${roundTripOutput}"'); |
| } |
| } |
| if (failures.isNotEmpty) { |
| print('Round trip failures:'); |
| failures.forEach(print); |
| throw StateError('Round trip failures'); |
| } |
| } |