| // 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(); |
| } |
| |
| class TestCase<T extends Node> { |
| final String name; |
| final T node; |
| final SerializationState Function() makeSerializationState; |
| final DeserializationState Function() makeDeserializationState; |
| final String expectation; |
| final TextSerializer<T> serializer; |
| |
| TestCase( |
| {this.name, |
| this.node, |
| this.expectation, |
| this.serializer, |
| SerializationState Function() makeSerializationState, |
| DeserializationState Function() makeDeserializationState}) |
| : assert(node != null), |
| assert(expectation != null), |
| assert(serializer != null), |
| this.makeSerializationState = makeSerializationState ?? |
| (() => new SerializationState(new SerializationEnvironment(null))), |
| this.makeDeserializationState = makeDeserializationState ?? |
| (() => new DeserializationState( |
| new DeserializationEnvironment(null), |
| new CanonicalName.root())); |
| |
| T readNode(String input, DeserializationState state) { |
| TextIterator stream = new TextIterator(input, 0); |
| stream.moveNext(); |
| T result = serializer.readFrom(stream, state); |
| if (stream.moveNext()) { |
| throw new StateError("Found extra tokens at the end."); |
| } |
| return result; |
| } |
| |
| String writeNode(T node, SerializationState state) { |
| StringBuffer buffer = new StringBuffer(); |
| serializer.writeTo(buffer, node, state); |
| return buffer.toString(); |
| } |
| } |
| |
| void test() { |
| List<String> failures = []; |
| List<TestCase> tests = <TestCase>[ |
| new TestCase<Statement>( |
| 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 "x^0" () (dynamic) (int 42) ()' |
| ' (get-var "x^0" _)))', |
| serializer: statementSerializer), |
| new TestCase<Statement>( |
| 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 "x^0" () (dynamic) (int 42) ()' |
| ' (let "x^1" () (bottom) (null) ()' |
| ' (get-var "x^0" _))))', |
| serializer: statementSerializer), |
| new TestCase<Statement>( |
| 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 "x^0" () (dynamic) (int 42) ()' |
| ' (let "x^1" () (bottom) (null) ()' |
| ' (get-var "x^1" _))))', |
| serializer: statementSerializer), |
| () { |
| VariableDeclaration x = |
| new VariableDeclaration('x', type: const DynamicType()); |
| return new TestCase<Statement>( |
| name: '/* suppose: dynamic x; */ x = 42;', |
| node: new ExpressionStatement(new VariableSet(x, new IntLiteral(42))), |
| expectation: '(expr (set-var "x^0" (int 42)))', |
| makeSerializationState: () => new SerializationState( |
| new SerializationEnvironment(null) |
| ..addBinder(x, nameClue: x.name) |
| ..extend(), |
| ), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null) |
| ..addBinder(x, "x^0") |
| ..extend(), |
| new CanonicalName.root()), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| name: '/* suppose top-level: dynamic field; */ field;', |
| node: new ExpressionStatement(new StaticGet(field)), |
| expectation: '' |
| '(expr (get-static "package:foo/bar.dart::@fields::field"))', |
| makeSerializationState: () => new SerializationState(null), |
| makeDeserializationState: () => |
| new DeserializationState(null, component.root), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| 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)))', |
| makeSerializationState: () => new SerializationState(null), |
| makeDeserializationState: () => |
| new DeserializationState(null, component.root), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| 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)) ()))', |
| makeSerializationState: () => new SerializationState(null), |
| makeDeserializationState: () => |
| new DeserializationState(null, component.root), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| 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"' |
| ' () () ()))', |
| makeSerializationState: () => new SerializationState(null), |
| makeDeserializationState: () => |
| new DeserializationState(null, component.root), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| name: '/* suppose A {dynamic field;} A x; */ x.{A::field};', |
| node: new ExpressionStatement(new PropertyGet.byReference( |
| new VariableGet(x), field.name, field.getterReference)), |
| expectation: '' |
| '(expr (get-prop (get-var "x^0" _) (public "field")))', |
| makeSerializationState: () => |
| new SerializationState(new SerializationEnvironment(null) |
| ..addBinder(x, nameClue: 'x') |
| ..extend()), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null) |
| ..addBinder(x, "x^0") |
| ..extend(), |
| component.root), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| name: '/* suppose A {dynamic field;} A x; */ x.{A::field} = 42;', |
| node: new ExpressionStatement(PropertySet.byReference( |
| new VariableGet(x), |
| field.name, |
| new IntLiteral(42), |
| field.setterReference)), |
| expectation: '' |
| '(expr (set-prop (get-var "x^0" _) (public "field") (int 42)))', |
| makeSerializationState: () => |
| new SerializationState(new SerializationEnvironment(null) |
| ..addBinder(x, nameClue: 'x') |
| ..extend()), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null) |
| ..addBinder(x, "x^0") |
| ..extend(), |
| component.root), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| name: '/* suppose A {foo() {...}} A x; */ x.{A::foo}();', |
| node: new ExpressionStatement(new MethodInvocation.byReference( |
| new VariableGet(x), |
| method.name, |
| new Arguments([]), |
| method.reference)), |
| expectation: '' |
| '(expr (invoke-method (get-var "x^0" _) (public "foo")' |
| ' () () ()))', |
| makeSerializationState: () => |
| new SerializationState(new SerializationEnvironment(null) |
| ..addBinder(x, nameClue: 'x') |
| ..extend()), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null) |
| ..addBinder(x, "x^0") |
| ..extend(), |
| component.root), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| 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"' |
| ' () () ()))', |
| makeSerializationState: () => new SerializationState(null), |
| makeDeserializationState: () => |
| new DeserializationState(null, component.root), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| 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"' |
| ' () () ()))', |
| makeSerializationState: () => new SerializationState(null), |
| makeDeserializationState: () => |
| new DeserializationState(null, component.root), |
| serializer: statementSerializer); |
| }(), |
| () { |
| TypeParameter outterParam = |
| new TypeParameter('T', const DynamicType(), const DynamicType()); |
| TypeParameter innerParam = |
| new TypeParameter('T', const DynamicType(), const DynamicType()); |
| return new TestCase<Statement>( |
| 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" _))))', |
| makeSerializationState: () => |
| new SerializationState(new SerializationEnvironment(null)), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null), null), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Statement>( |
| 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)) ("t1^1" ' |
| '() (par "T^0" _) _ ()) ("t2^2" () (par "T^0" _) ' |
| '_ ()) () (par "T^0" _) _ (ret (get-var "t1^1" _))))', |
| makeSerializationState: () => |
| new SerializationState(new SerializationEnvironment(null)), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null), null), |
| serializer: statementSerializer); |
| }(), |
| () { |
| 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 TestCase<Member>( |
| name: 'foo(x) => x;', |
| node: foo, |
| expectation: '' |
| '(method (public "foo") ((static))' |
| ' (sync) () () () ("x^0" () (dynamic) _ ()) () ()' |
| ' (dynamic) _ (ret (get-var "x^0" _)))', |
| makeSerializationState: () => |
| new SerializationState(new SerializationEnvironment(null)), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null), null), |
| serializer: memberSerializer); |
| }(), |
| () { |
| VariableDeclaration x1 = VariableDeclaration('x', type: DynamicType()); |
| VariableDeclaration x2 = VariableDeclaration('x', type: DynamicType()); |
| Procedure foo = Procedure( |
| Name('foo'), |
| ProcedureKind.Method, |
| FunctionNode(ReturnStatement(VariableGet(x1)), |
| positionalParameters: [x1]), |
| isStatic: true); |
| Procedure bar = Procedure( |
| Name('bar'), |
| ProcedureKind.Method, |
| FunctionNode( |
| ReturnStatement( |
| StaticInvocation(foo, Arguments([VariableGet(x2)]))), |
| positionalParameters: [x2]), |
| isStatic: true); |
| Library library = Library(Uri(scheme: 'package', path: 'foo/bar.dart'), |
| procedures: [foo, bar]); |
| Component component = Component(libraries: [library]); |
| component.computeCanonicalNames(); |
| return new TestCase<Library>( |
| name: 'foo(x) => x; bar(x) => foo(x);', |
| node: library, |
| expectation: '' |
| '"package:foo/bar.dart" () ()' |
| '' |
| ' ((method (public "foo") ((static))' |
| ' (sync) () () () ("x^0" () (dynamic) _ ()) () () (dynamic)' |
| ' _ (ret (get-var "x^0" _)))' |
| '' |
| ' (method (public "bar") ((static))' |
| ' (sync) () () () ("x^0" () (dynamic) _ ()) () () (dynamic)' |
| ' _ (ret' |
| ' (invoke-static "package:foo/bar.dart::@methods::foo"' |
| ' () ((get-var "x^0" _)) ()))))' |
| '' |
| ' ()' |
| '' |
| ' ()' |
| '' |
| ' ()', |
| makeSerializationState: () => |
| new SerializationState(new SerializationEnvironment(null)), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null), new CanonicalName.root()), |
| serializer: librarySerializer); |
| }(), |
| () { |
| Class a = Class(name: "A"); |
| Procedure foo = Procedure( |
| Name("foo"), |
| ProcedureKind.Method, |
| FunctionNode(ReturnStatement(NullLiteral()), |
| returnType: InterfaceType(a, Nullability.legacy)), |
| isStatic: true); |
| Library library = Library(Uri(scheme: "package", path: "foo/bar.dart"), |
| classes: [a], procedures: [foo]); |
| Component component = Component(libraries: [library]); |
| component.computeCanonicalNames(); |
| return new TestCase<Library>( |
| name: 'class A{} A foo() => null;', |
| node: library, |
| expectation: '' |
| '"package:foo/bar.dart" () ()' |
| '' |
| ' ((method (public "foo") ((static))' |
| ' (sync) () () () () () () (interface "package:foo/bar.dart::A" ())' |
| ' _ (ret (null))))' |
| '' |
| ' ("A" () () () () _ _ () ())' |
| '' |
| ' ()' |
| '' |
| ' ()', |
| makeSerializationState: () => |
| new SerializationState(new SerializationEnvironment(null)), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null), component.root), |
| serializer: librarySerializer); |
| }(), |
| () { |
| return new TestCase<Statement>( |
| name: 'dynamic x;', |
| node: VariableDeclaration('x', type: const DynamicType()), |
| expectation: '(local "x^0" () (dynamic) _ ())', |
| makeSerializationState: () => |
| new SerializationState(new SerializationEnvironment(null)), |
| makeDeserializationState: () => new DeserializationState( |
| new DeserializationEnvironment(null), new CanonicalName.root()), |
| serializer: statementSerializer); |
| }(), |
| ]; |
| for (TestCase testCase in tests) { |
| String roundTripInput = |
| testCase.writeNode(testCase.node, testCase.makeSerializationState()); |
| if (roundTripInput != testCase.expectation) { |
| failures.add('' |
| "* initial serialization for test '${testCase.name}'" |
| " gave output '${roundTripInput}'" |
| " but expected '${testCase.expectation}'"); |
| } |
| |
| TreeNode deserialized = |
| testCase.readNode(roundTripInput, testCase.makeDeserializationState()); |
| String roundTripOutput = |
| testCase.writeNode(deserialized, testCase.makeSerializationState()); |
| 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'); |
| } |
| } |