// 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. // @dart = 2.9 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 { final String name; final T node; final SerializationState Function() makeSerializationState; final DeserializationState Function() makeDeserializationState; final String expectation; final TextSerializer 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 failures = []; List tests = [ new TestCase( 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( name: 'let dynamic x = 42 in let Null 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 NullType(), 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" () (null-type) (null) ()' ' (get-var "x^0" _))))', serializer: statementSerializer), new TestCase( name: 'let dynamic x = 42 in let Null 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 NullType(), 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" () (null-type) (null) ()' ' (get-var "x^1" _))))', serializer: statementSerializer), () { VariableDeclaration x = new VariableDeclaration('x', type: const DynamicType()); return new TestCase( 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); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Field field = new Field.immutable(new Name('field'), type: const DynamicType(), fileUri: uri); Library library = new Library(uri, fileUri: uri, fields: [field]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( name: '/* suppose top-level: dynamic field; */ field;', node: new ExpressionStatement(new StaticGet(field)), expectation: '' '(expr (get-static "package:foo/bar.dart::@getters::field"))', makeSerializationState: () => new SerializationState(null), makeDeserializationState: () => new DeserializationState(null, component.root), serializer: statementSerializer); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Field field = new Field.mutable(new Name('field'), type: const DynamicType(), fileUri: uri); Library library = new Library(uri, fileUri: uri, fields: [field]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( name: '/* suppose top-level: dynamic field; */ field;', node: new ExpressionStatement(new StaticGet(field)), expectation: '' '(expr (get-static "package:foo/bar.dart::@getters::field"))', makeSerializationState: () => new SerializationState(null), makeDeserializationState: () => new DeserializationState(null, component.root), serializer: statementSerializer); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Field field = new Field.mutable(new Name('field'), type: const DynamicType(), fileUri: uri); Library library = new Library(uri, fileUri: uri, fields: [field]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( 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::@setters::field" (int 1)))', makeSerializationState: () => new SerializationState(null), makeDeserializationState: () => new DeserializationState(null, component.root), serializer: statementSerializer); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Procedure topLevelProcedure = new Procedure( new Name('foo'), ProcedureKind.Method, new FunctionNode(null, positionalParameters: [ new VariableDeclaration('x', type: const DynamicType()) ]), isStatic: true, fileUri: uri); Library library = new Library(uri, fileUri: uri, procedures: [topLevelProcedure]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( name: '/* suppose top-level: foo(dynamic x) {...}; */ foo(42);', node: new ExpressionStatement(new StaticInvocation.byReference( topLevelProcedure.reference, new Arguments([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); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Procedure factoryConstructor = new Procedure( new Name('foo'), ProcedureKind.Factory, new FunctionNode(null), isStatic: true, isConst: true, fileUri: uri); Class klass = new Class( name: 'A', procedures: [factoryConstructor], fileUri: uri); Library library = new Library(uri, fileUri: uri, classes: [klass]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( 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); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Field field = new Field.immutable(new Name('field'), type: const DynamicType(), fileUri: uri); Class klass = new Class(name: 'A', fields: [field], fileUri: uri); Library library = new Library(uri, fileUri: uri, classes: [klass]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); VariableDeclaration x = new VariableDeclaration('x', type: const DynamicType()); return new TestCase( 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); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Field field = new Field.mutable(new Name('field'), type: const DynamicType(), fileUri: uri); Class klass = new Class(name: 'A', fields: [field], fileUri: uri); Library library = new Library(uri, fileUri: uri, classes: [klass]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); VariableDeclaration x = new VariableDeclaration('x', type: const DynamicType()); return new TestCase( 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); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Field field = new Field.mutable(new Name('field'), type: const DynamicType(), fileUri: uri); Class klass = new Class(name: 'A', fields: [field], fileUri: uri); Library library = new Library(uri, fileUri: uri, classes: [klass]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); VariableDeclaration x = new VariableDeclaration('x', type: const DynamicType()); return new TestCase( 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); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Procedure method = new Procedure( new Name('foo'), ProcedureKind.Method, new FunctionNode(null), isStatic: true, isConst: true, fileUri: uri); Class klass = new Class(name: 'A', procedures: [method], fileUri: uri); Library library = new Library(uri, fileUri: uri, classes: [klass]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); VariableDeclaration x = new VariableDeclaration('x', type: const DynamicType()); return new TestCase( 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); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Constructor constructor = new Constructor(new FunctionNode(null), name: new Name('foo'), fileUri: uri); Class klass = new Class( name: 'A', constructors: [constructor], fileUri: uri); Library library = new Library(uri, fileUri: uri, classes: [klass]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( 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); }(), () { Uri uri = new Uri(scheme: 'package', path: 'foo/bar.dart'); Constructor constructor = new Constructor(new FunctionNode(null), name: new Name('foo'), isConst: true, fileUri: uri); Class klass = new Class( name: 'A', constructors: [constructor], fileUri: uri); Library library = new Library(uri, fileUri: uri, classes: [klass]); Component component = new Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( 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( name: '/* T Function(T Function()); */', 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( name: '/* (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); }(), () { Uri uri = Uri(scheme: 'package', path: 'foo/bar.dart'); VariableDeclaration x = VariableDeclaration('x', type: DynamicType()); Procedure foo = Procedure( Name('foo'), ProcedureKind.Method, FunctionNode(ReturnStatement(VariableGet(x)), positionalParameters: [x]), isStatic: true, fileUri: uri); Library library = Library(uri, fileUri: uri, procedures: [foo]); Component component = Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( name: 'foo(x) => x;', node: foo, expectation: '' '(method (public "foo") ((static))' ' (sync) () () () ("x^0" () (dynamic) _ ()) () ()' ' (dynamic) _ (ret (get-var "x^0" _))' ' "package:foo/bar.dart")', makeSerializationState: () => new SerializationState(new SerializationEnvironment(null)), makeDeserializationState: () => new DeserializationState( new DeserializationEnvironment(null), null), serializer: memberSerializer); }(), () { Uri uri = Uri(scheme: 'package', path: 'foo/bar.dart'); 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, fileUri: uri); Procedure bar = Procedure( Name('bar'), ProcedureKind.Method, FunctionNode( ReturnStatement( StaticInvocation(foo, Arguments([VariableGet(x2)]))), positionalParameters: [x2]), isStatic: true, fileUri: uri); Library library = Library(uri, fileUri: uri, procedures: [foo, bar]); Component component = Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( 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" _))' ' "package:foo/bar.dart")' '' ' (method (public "bar") ((static))' ' (sync) () () () ("x^0" () (dynamic) _ ()) () () (dynamic)' ' _ (ret' ' (invoke-static "package:foo/bar.dart::@methods::foo"' ' () ((get-var "x^0" _)) ()))' ' "package:foo/bar.dart"))' '' ' ()' '' ' ()' '' ' ()' '' ' "package:foo/bar.dart"', makeSerializationState: () => new SerializationState(new SerializationEnvironment(null)), makeDeserializationState: () => new DeserializationState( new DeserializationEnvironment(null), new CanonicalName.root()), serializer: librarySerializer); }(), () { Uri uri = Uri(scheme: "package", path: "foo/bar.dart"); Class a = Class(name: "A", fileUri: uri); Procedure foo = Procedure( Name("foo"), ProcedureKind.Method, FunctionNode(ReturnStatement(NullLiteral()), returnType: InterfaceType(a, Nullability.legacy)), isStatic: true, fileUri: uri); Library library = Library(uri, fileUri: uri, classes: [a], procedures: [foo]); Component component = Component(libraries: [library]); component.computeCanonicalNames(); return new TestCase( 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))' ' "package:foo/bar.dart"))' '' ' ("A" () "package:foo/bar.dart" () () () _ _ () ())' '' ' ()' '' ' ()' '' ' "package:foo/bar.dart"', makeSerializationState: () => new SerializationState(new SerializationEnvironment(null)), makeDeserializationState: () => new DeserializationState( new DeserializationEnvironment(null), component.root), serializer: librarySerializer); }(), () { return new TestCase( 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:\n ${roundTripInput}\n" " but expected:\n ${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'); } }