| // Copyright (c) 2016, 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. | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/target/targets.dart'; | |
| import 'package:kernel/text/ast_to_text.dart'; | |
| import 'package:kernel/verifier.dart'; | |
| import 'package:test/test.dart'; | |
| const int dummyFileOffset = 4312; | |
| String errorPrefix = | |
| 'Target=none, VerificationStage.afterModularTransformations: '; | |
| /// Checks that the verifier correctly find errors in invalid components. | |
| /// | |
| /// The frontend should never generate invalid components, so we have to test | |
| /// these by manually constructing invalid ASTs. | |
| /// | |
| /// We mostly test negative cases here, as we get plenty of positive cases by | |
| /// compiling the Dart test suite with the verifier enabled. | |
| void main() { | |
| positiveTest('Test harness has no errors', (TestHarness test) { | |
| test.addNode(NullLiteral()); | |
| }); | |
| negative1Test('VariableGet out of scope', (TestHarness test) { | |
| Variable node = test.makeVariable(); | |
| test.addNode(VariableGet(node)); | |
| return node; | |
| }, (Node? node) => "${errorPrefix}Variable '$node' used out of scope."); | |
| negative1Test('VariableSet out of scope', (TestHarness test) { | |
| Variable variable = test.makeVariable(); | |
| test.addNode(VariableSet(variable, new NullLiteral())); | |
| return variable; | |
| }, (Node? node) => "${errorPrefix}Variable '$node' used out of scope."); | |
| negative1Test('Variable block scope', (TestHarness test) { | |
| DeclaredVariable variable = test.makeVariable(); | |
| test.addNode( | |
| Block([ | |
| new Block([new VariableStatement(VariableDeclaration(variable))]), | |
| new ReturnStatement(new VariableGet(variable)), | |
| ]), | |
| ); | |
| return variable; | |
| }, (Node? node) => "${errorPrefix}Variable '$node' used out of scope."); | |
| negative1Test('Variable let scope', (TestHarness test) { | |
| SyntheticVariable variable = test.makeVariable(); | |
| test.addNode( | |
| LogicalExpression( | |
| new Let( | |
| variable: variable, | |
| value: new NullLiteral(), | |
| body: new VariableGet(variable), | |
| ), | |
| LogicalExpressionOperator.AND, | |
| new VariableGet(variable), | |
| ), | |
| ); | |
| return variable; | |
| }, (Node? node) => "${errorPrefix}Variable '$node' used out of scope."); | |
| negative1Test('Variable redeclared', (TestHarness test) { | |
| PositionalParameter variable = test.makePositionalParameter('p'); | |
| test.addNode( | |
| Procedure( | |
| new Name('bar'), | |
| ProcedureKind.Method, | |
| FunctionNode( | |
| null, | |
| positionalParameters: PositionalParameterList(variable, variable), | |
| ), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset, | |
| ); | |
| return variable; | |
| }, (Node? node) => "${errorPrefix}Variable '$node' declared more than once."); | |
| negative1Test( | |
| 'Member redeclared', | |
| (TestHarness test) { | |
| Field field = new Field.mutable( | |
| new Name('field'), | |
| initializer: new NullLiteral(), | |
| fileUri: dummyUri, | |
| ); | |
| test.addNode( | |
| Class( | |
| name: 'Test', | |
| supertype: test.objectClass.asRawSupertype, | |
| fields: [field, field], | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset, | |
| ); | |
| return field; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}Member '$node' has been declared more than once.", | |
| ); | |
| negative1Test('Class redeclared', (TestHarness test) { | |
| Class otherClass = test.otherClass; | |
| test.addNode(otherClass); // Test harness also adds otherClass to component. | |
| return test.otherClass; | |
| }, (Node? node) => "${errorPrefix}Class '$node' declared more than once."); | |
| negative1Test('Class type parameter redeclared', (TestHarness test) { | |
| TypeParameter parameter = test.makeTypeParameter(); | |
| test.addNode( | |
| Class( | |
| name: 'Test', | |
| supertype: test.objectClass.asRawSupertype, | |
| typeParameters: new TypeParameterList(parameter, parameter), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset, | |
| ); | |
| return parameter; | |
| }, (Node? node) => "${errorPrefix}Type parameter '$node' redeclared."); | |
| negative1Test('Member type parameter redeclared', (TestHarness test) { | |
| TypeParameter parameter = test.makeTypeParameter(); | |
| test.addNode( | |
| Procedure( | |
| new Name('bar'), | |
| ProcedureKind.Method, | |
| new FunctionNode( | |
| new ReturnStatement(new NullLiteral()), | |
| typeParameters: new TypeParameterList(parameter, parameter), | |
| ), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset, | |
| ); | |
| return parameter; | |
| }, (Node? node) => "${errorPrefix}Type parameter '$node' redeclared."); | |
| negative2Test( | |
| 'Type parameter out of scope', | |
| (TestHarness test) { | |
| TypeParameter parameter = test.makeTypeParameter(); | |
| test.addNode( | |
| ListLiteral( | |
| ExpressionList.empty, | |
| typeArgument: new TypeParameterType( | |
| parameter, | |
| Nullability.nonNullable, | |
| ), | |
| ), | |
| ); | |
| return [parameter, null]; | |
| }, | |
| (Node? node, Node? parent) => | |
| "${errorPrefix}Type parameter '$node' referenced out of scope," | |
| " declaration is: '$parent'.", | |
| ); | |
| negative2Test( | |
| 'Class type parameter from another class', | |
| (TestHarness test) { | |
| TypeParameter node = test.otherClass.typeParameters[0]; | |
| test.addNode( | |
| TypeLiteral(new TypeParameterType(node, Nullability.nonNullable)), | |
| ); | |
| return [node, test.otherClass]; | |
| }, | |
| (Node? node, Node? parent) => | |
| "${errorPrefix}Type parameter '$node' referenced out of scope," | |
| " declaration is: '$parent'.", | |
| ); | |
| negative2Test( | |
| 'Class type parameter in static method', | |
| (TestHarness test) { | |
| TypeParameter node = test.classTypeParameter; | |
| test.addNode( | |
| Procedure( | |
| new Name('bar'), | |
| ProcedureKind.Method, | |
| new FunctionNode( | |
| new ReturnStatement( | |
| new TypeLiteral( | |
| new TypeParameterType(node, Nullability.nonNullable), | |
| ), | |
| ), | |
| ), | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset, | |
| ); | |
| return [node, test.enclosingClass]; | |
| }, | |
| (Node? node, Node? parent) => | |
| "${errorPrefix}Type parameter '$node' referenced from static context," | |
| " declaration is: '$parent'.", | |
| ); | |
| negative2Test( | |
| 'Class type parameter in static field', | |
| (TestHarness test) { | |
| TypeParameter node = test.classTypeParameter; | |
| test.addNode( | |
| Field.mutable( | |
| new Name('field'), | |
| initializer: new TypeLiteral( | |
| new TypeParameterType(node, Nullability.nonNullable), | |
| ), | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset, | |
| ); | |
| return [node, test.enclosingClass]; | |
| }, | |
| (Node? node, Node? parent) => | |
| "${errorPrefix}Type parameter '$node' referenced from static context," | |
| " declaration is: '$parent'.", | |
| ); | |
| negative2Test( | |
| 'Method type parameter out of scope', | |
| (TestHarness test) { | |
| TypeParameter parameter = test.makeTypeParameter(); | |
| FunctionNode parent = new FunctionNode( | |
| new EmptyStatement(), | |
| typeParameters: new TypeParameterList(parameter), | |
| ); | |
| test.addNode( | |
| Class( | |
| name: 'Test', | |
| supertype: test.objectClass.asRawSupertype, | |
| procedures: [ | |
| new Procedure( | |
| new Name('generic'), | |
| ProcedureKind.Method, | |
| parent, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset, | |
| new Procedure( | |
| new Name('use'), | |
| ProcedureKind.Method, | |
| new FunctionNode( | |
| new ReturnStatement( | |
| new TypeLiteral( | |
| new TypeParameterType(parameter, Nullability.nonNullable), | |
| ), | |
| ), | |
| ), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset, | |
| ], | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset, | |
| ); | |
| return [parameter, parent]; | |
| }, | |
| (Node? node, Node? parent) => | |
| "${errorPrefix}Type parameter '$node' referenced out of scope," | |
| " declaration is: '${(parent as TreeNode).parent}'.", | |
| ); | |
| negative1Test( | |
| 'Interface type arity too low', | |
| (TestHarness test) { | |
| InterfaceType node = new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| DartTypeList.empty, | |
| ); | |
| test.addNode(TypeLiteral(node)); | |
| return node; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}Type $node provides 0 type arguments," | |
| " but the class declares 1 parameters.", | |
| ); | |
| negative1Test( | |
| 'Interface type arity too high', | |
| (TestHarness test) { | |
| InterfaceType node = new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| DartTypeList.dynamic2, | |
| ); | |
| test.addNode(TypeLiteral(node)); | |
| return node; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}Type $node provides 2 type arguments," | |
| " but the class declares 1 parameters.", | |
| ); | |
| negative1Test( | |
| 'Dangling interface type', | |
| (TestHarness test) { | |
| Class orphan = new Class(name: 'Class', fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| test.addNode( | |
| new TypeLiteral(new InterfaceType(orphan, Nullability.nonNullable)), | |
| ); | |
| return orphan; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}Dangling reference to '$node', parent is: 'null'.", | |
| ); | |
| negative1Test( | |
| 'Dangling field get', | |
| (TestHarness test) { | |
| Field orphan = new Field.mutable(new Name('foo'), fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| test.addNode(new StaticGet(orphan)); | |
| return orphan; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}Dangling reference to '$node', parent is: 'null'.", | |
| ); | |
| simpleNegativeTest( | |
| 'Missing block parent pointer', | |
| "${errorPrefix}Incorrect parent pointer on ReturnStatement(return;):" | |
| """ expected Block({ | |
| return; | |
| }), but found: null.""", | |
| (TestHarness test) { | |
| var block = new Block([]); | |
| block.statements.add(new ReturnStatement()); | |
| test.addNode(block); | |
| }, | |
| ); | |
| simpleNegativeTest( | |
| 'Missing function parent pointer', | |
| "${errorPrefix}Incorrect parent pointer on FunctionNode():" | |
| " expected TestClass.bar, but found: null.", | |
| (TestHarness test) { | |
| var procedure = new Procedure( | |
| new Name('bar'), | |
| ProcedureKind.Method, | |
| dummyFunctionNode, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| procedure.function = new FunctionNode(new EmptyStatement()); | |
| test.addNode(procedure); | |
| }, | |
| ); | |
| positiveTest('Correct StaticInvocation', (TestHarness test) { | |
| var method = new Procedure( | |
| new Name('foo'), | |
| ProcedureKind.Method, | |
| new FunctionNode( | |
| new EmptyStatement(), | |
| positionalParameters: new PositionalParameterList( | |
| new PositionalParameter(parameterName: 'p'), | |
| ), | |
| ), | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingClass.addProcedure(method); | |
| test.addNode( | |
| StaticInvocation( | |
| method, | |
| new Arguments(new ExpressionList(new NullLiteral())), | |
| ), | |
| ); | |
| }); | |
| negative1Test( | |
| 'StaticInvocation with too many parameters', | |
| (TestHarness test) { | |
| var method = new Procedure( | |
| new Name('bar'), | |
| ProcedureKind.Method, | |
| new FunctionNode(new EmptyStatement()), | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingClass.addProcedure(method); | |
| test.addNode( | |
| StaticInvocation( | |
| method, | |
| new Arguments(new ExpressionList(new NullLiteral())), | |
| ), | |
| ); | |
| return method; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}StaticInvocation with incompatible arguments for" | |
| " '$node'.", | |
| ); | |
| negative1Test( | |
| 'StaticInvocation with too few parameters', | |
| (TestHarness test) { | |
| var method = new Procedure( | |
| new Name('bar'), | |
| ProcedureKind.Method, | |
| new FunctionNode( | |
| new EmptyStatement(), | |
| positionalParameters: new PositionalParameterList( | |
| new PositionalParameter(parameterName: 'p'), | |
| ), | |
| ), | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingClass.addProcedure(method); | |
| test.addNode(StaticInvocation(method, new Arguments.empty())); | |
| return method; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}StaticInvocation with incompatible arguments for " | |
| "'$node'.", | |
| ); | |
| negative1Test( | |
| 'StaticInvocation with unmatched named parameter', | |
| (TestHarness test) { | |
| var method = new Procedure( | |
| new Name('bar'), | |
| ProcedureKind.Method, | |
| new FunctionNode(new EmptyStatement()), | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingClass.addProcedure(method); | |
| test.addNode( | |
| StaticInvocation( | |
| method, | |
| new Arguments( | |
| ExpressionList.empty, | |
| named: new NamedExpressionList( | |
| new NamedExpression('p', new NullLiteral()), | |
| ), | |
| ), | |
| ), | |
| ); | |
| return method; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}StaticInvocation with incompatible arguments for" | |
| " '$node'.", | |
| ); | |
| negative1Test( | |
| 'StaticInvocation with missing type argument', | |
| (TestHarness test) { | |
| Procedure method = new Procedure( | |
| new Name('bar'), | |
| ProcedureKind.Method, | |
| new FunctionNode( | |
| new EmptyStatement(), | |
| typeParameters: new TypeParameterList(test.makeTypeParameter()), | |
| ), | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingClass.addProcedure(method); | |
| test.addNode(StaticInvocation(method, new Arguments.empty())); | |
| return method; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}StaticInvocation with wrong number of type arguments for" | |
| " '$node'.", | |
| ); | |
| negative1Test( | |
| 'ConstructorInvocation with missing type argument', | |
| (TestHarness test) { | |
| var constructor = new Constructor( | |
| new FunctionNode(new EmptyStatement()), | |
| name: new Name('foo'), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingClass.addConstructor(constructor); | |
| test.addNode(ConstructorInvocation(constructor, new Arguments.empty())); | |
| return constructor; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}ConstructorInvocation with wrong number of type " | |
| "arguments for '$node'.", | |
| ); | |
| positiveTest('Valid typedef Foo = `(C) => void`', (TestHarness test) { | |
| var typedef_ = new Typedef( | |
| 'Foo', | |
| new FunctionType( | |
| new DartTypeList(test.otherRawType), | |
| const VoidType(), | |
| Nullability.nonNullable, | |
| ), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.addNode(typedef_); | |
| }); | |
| positiveTest('Valid typedef Foo = C<dynamic>', (TestHarness test) { | |
| var typedef_ = new Typedef( | |
| 'Foo', | |
| new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| DartTypeList.dynamic1, | |
| ), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.addNode(typedef_); | |
| }); | |
| positiveTest('Valid typedefs Foo = Bar, Bar = C', (TestHarness test) { | |
| var foo = new Typedef('Foo', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| var bar = new Typedef('Bar', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| foo.type = new TypedefType(bar, Nullability.nonNullable); | |
| bar.type = test.otherRawType; | |
| test.enclosingLibrary.addTypedef(foo); | |
| test.enclosingLibrary.addTypedef(bar); | |
| }); | |
| positiveTest('Valid typedefs Foo = C<Bar>, Bar = C', (TestHarness test) { | |
| var foo = new Typedef('Foo', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| var bar = new Typedef('Bar', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| foo.type = new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(new TypedefType(bar, Nullability.nonNullable)), | |
| ); | |
| bar.type = test.otherRawType; | |
| test.enclosingLibrary.addTypedef(foo); | |
| test.enclosingLibrary.addTypedef(bar); | |
| }); | |
| positiveTest('Valid typedef type in field', (TestHarness test) { | |
| var typedef_ = new Typedef( | |
| 'Foo', | |
| new FunctionType( | |
| new DartTypeList(test.otherRawType), | |
| const VoidType(), | |
| Nullability.nonNullable, | |
| ), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| var field = new Field.mutable( | |
| new Name('field'), | |
| type: new TypedefType(typedef_, Nullability.nonNullable), | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingLibrary.addTypedef(typedef_); | |
| test.enclosingLibrary.addField(field); | |
| }); | |
| negative1Test('Invalid typedef Foo = Foo', (TestHarness test) { | |
| var typedef_ = new Typedef('Foo', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| typedef_.type = new TypedefType(typedef_, Nullability.nonNullable); | |
| test.addNode(typedef_); | |
| return typedef_; | |
| }, (Node? node) => "${errorPrefix}The typedef '$node' refers to itself"); | |
| negative1Test('Invalid typedef Foo = `(Foo) => void`', (TestHarness test) { | |
| var typedef_ = new Typedef('Foo', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| typedef_.type = new FunctionType( | |
| new DartTypeList(new TypedefType(typedef_, Nullability.nonNullable)), | |
| const VoidType(), | |
| Nullability.nonNullable, | |
| ); | |
| test.addNode(typedef_); | |
| return typedef_; | |
| }, (Node? node) => "${errorPrefix}The typedef '$node' refers to itself"); | |
| negative1Test('Invalid typedef Foo = `() => Foo`', (TestHarness test) { | |
| var typedef_ = new Typedef('Foo', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| typedef_.type = new FunctionType( | |
| DartTypeList.empty, | |
| new TypedefType(typedef_, Nullability.nonNullable), | |
| Nullability.nonNullable, | |
| ); | |
| test.addNode(typedef_); | |
| return typedef_; | |
| }, (Node? node) => "${errorPrefix}The typedef '$node' refers to itself"); | |
| negative1Test('Invalid typedef Foo = C<Foo>', (TestHarness test) { | |
| var typedef_ = new Typedef('Foo', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| typedef_.type = new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(new TypedefType(typedef_, Nullability.nonNullable)), | |
| ); | |
| test.addNode(typedef_); | |
| return typedef_; | |
| }, (Node? node) => "${errorPrefix}The typedef '$node' refers to itself"); | |
| negative1Test('Invalid typedefs Foo = Bar, Bar = Foo', (TestHarness test) { | |
| var foo = new Typedef('Foo', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| var bar = new Typedef('Bar', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| foo.type = new TypedefType(bar, Nullability.nonNullable); | |
| bar.type = new TypedefType(foo, Nullability.nonNullable); | |
| test.enclosingLibrary.addTypedef(foo); | |
| test.enclosingLibrary.addTypedef(bar); | |
| return foo; | |
| }, (Node? foo) => "${errorPrefix}The typedef '$foo' refers to itself"); | |
| negative1Test('Invalid typedefs Foo = Bar, Bar = C<Foo>', (TestHarness test) { | |
| var foo = new Typedef('Foo', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| var bar = new Typedef('Bar', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| foo.type = new TypedefType(bar, Nullability.nonNullable); | |
| bar.type = new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(new TypedefType(foo, Nullability.nonNullable)), | |
| ); | |
| test.enclosingLibrary.addTypedef(foo); | |
| test.enclosingLibrary.addTypedef(bar); | |
| return foo; | |
| }, (Node? foo) => "${errorPrefix}The typedef '$foo' refers to itself"); | |
| negative1Test('Invalid typedefs Foo = C<Bar>, Bar = C<Foo>', ( | |
| TestHarness test, | |
| ) { | |
| var foo = new Typedef('Foo', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| var bar = new Typedef('Bar', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| foo.type = new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(new TypedefType(bar, Nullability.nonNullable)), | |
| ); | |
| bar.type = new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(new TypedefType(foo, Nullability.nonNullable)), | |
| ); | |
| test.enclosingLibrary.addTypedef(foo); | |
| test.enclosingLibrary.addTypedef(bar); | |
| return foo; | |
| }, (Node? foo) => "${errorPrefix}The typedef '$foo' refers to itself"); | |
| positiveTest('Valid long typedefs C20 = C19 = ... = C1 = C0 = dynamic', ( | |
| TestHarness test, | |
| ) { | |
| var typedef_ = new Typedef('C0', const DynamicType(), fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| test.enclosingLibrary.addTypedef(typedef_); | |
| for (int i = 1; i < 20; ++i) { | |
| typedef_ = new Typedef( | |
| 'C$i', | |
| new TypedefType(typedef_, Nullability.nonNullable), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingLibrary.addTypedef(typedef_); | |
| } | |
| }); | |
| negative1Test('Invalid long typedefs C20 = C19 = ... = C1 = C0 = C20', ( | |
| TestHarness test, | |
| ) { | |
| Typedef firstTypedef = new Typedef('C0', null, fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| Typedef typedef_ = firstTypedef; | |
| test.enclosingLibrary.addTypedef(typedef_); | |
| var first = typedef_; | |
| for (int i = 1; i < 20; ++i) { | |
| typedef_ = new Typedef( | |
| 'C$i', | |
| new TypedefType(typedef_, Nullability.nonNullable), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingLibrary.addTypedef(typedef_); | |
| } | |
| first.type = new TypedefType(typedef_, Nullability.nonNullable); | |
| return firstTypedef; | |
| }, (Node? node) => "${errorPrefix}The typedef '$node' refers to itself"); | |
| positiveTest('Valid typedef Foo<T extends C> = C<T>', (TestHarness test) { | |
| var param = new TypeParameter('T', test.otherRawType, test.otherRawType); | |
| var foo = new Typedef( | |
| 'Foo', | |
| new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(new TypeParameterType(param, Nullability.nonNullable)), | |
| ), | |
| typeParameters: new TypeParameterList(param), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.addNode(foo); | |
| }); | |
| positiveTest('Valid typedef Foo<T extends C<T>> = C<T>', (TestHarness test) { | |
| var param = new TypeParameter('T', test.otherRawType, test.otherRawType); | |
| param.bound = new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(new TypeParameterType(param, Nullability.nonNullable)), | |
| ); | |
| var foo = new Typedef( | |
| 'Foo', | |
| new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(new TypeParameterType(param, Nullability.nonNullable)), | |
| ), | |
| typeParameters: new TypeParameterList(param), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.addNode(foo); | |
| }); | |
| positiveTest('Valid typedef Foo<T> = dynamic, Bar<T extends Foo<T>> = C<T>', ( | |
| TestHarness test, | |
| ) { | |
| var fooParam = test.makeTypeParameter('T'); | |
| var foo = new Typedef( | |
| 'Foo', | |
| const DynamicType(), | |
| typeParameters: new TypeParameterList(fooParam), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| var barParam = new TypeParameter('T', null); | |
| barParam.bound = new TypedefType( | |
| foo, | |
| Nullability.nonNullable, | |
| new DartTypeList( | |
| new TypeParameterType(barParam, Nullability.nonNullable), | |
| ), | |
| ); | |
| barParam.defaultType = const DynamicType(); | |
| var bar = new Typedef( | |
| 'Bar', | |
| new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList( | |
| new TypeParameterType(barParam, Nullability.nonNullable), | |
| ), | |
| ), | |
| typeParameters: new TypeParameterList(barParam), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingLibrary.addTypedef(foo); | |
| test.enclosingLibrary.addTypedef(bar); | |
| }); | |
| negative1Test( | |
| 'Invalid typedefs Foo<T extends Bar<T>>, Bar<T extends Foo<T>>', | |
| (TestHarness test) { | |
| var fooParam = test.makeTypeParameter('T'); | |
| var foo = new Typedef( | |
| 'Foo', | |
| const DynamicType(), | |
| typeParameters: new TypeParameterList(fooParam), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| var barParam = new TypeParameter('T', null); | |
| barParam.bound = new TypedefType( | |
| foo, | |
| Nullability.nonNullable, | |
| new DartTypeList( | |
| new TypeParameterType(barParam, Nullability.nonNullable), | |
| ), | |
| ); | |
| barParam.defaultType = const DynamicType(); | |
| var bar = new Typedef( | |
| 'Bar', | |
| new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList( | |
| new TypeParameterType(barParam, Nullability.nonNullable), | |
| ), | |
| ), | |
| typeParameters: new TypeParameterList(barParam), | |
| fileUri: dummyUri, | |
| ); | |
| fooParam.bound = new TypedefType( | |
| bar, | |
| Nullability.nonNullable, | |
| new DartTypeList( | |
| new TypeParameterType(fooParam, Nullability.nonNullable), | |
| ), | |
| ); | |
| fooParam.defaultType = const DynamicType(); | |
| test.enclosingLibrary.addTypedef(foo); | |
| test.enclosingLibrary.addTypedef(bar); | |
| return foo; | |
| }, | |
| (Node? foo) => "${errorPrefix}The typedef '$foo' refers to itself", | |
| ); | |
| negative1Test('Invalid typedef Foo<T extends Foo<dynamic> = C<T>', ( | |
| TestHarness test, | |
| ) { | |
| var param = new TypeParameter('T', null); | |
| var foo = new Typedef( | |
| 'Foo', | |
| new InterfaceType( | |
| test.otherClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(new TypeParameterType(param, Nullability.nonNullable)), | |
| ), | |
| typeParameters: new TypeParameterList(param), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| param.bound = new TypedefType( | |
| foo, | |
| Nullability.nonNullable, | |
| DartTypeList.dynamic1, | |
| ); | |
| param.defaultType = const DynamicType(); | |
| test.addNode(foo); | |
| return foo; | |
| }, (Node? foo) => "${errorPrefix}The typedef '$foo' refers to itself"); | |
| negative1Test( | |
| 'Typedef arity error', | |
| (TestHarness test) { | |
| var param = test.makeTypeParameter('T'); | |
| var foo = new Typedef( | |
| 'Foo', | |
| test.otherRawType, | |
| typeParameters: new TypeParameterList(param), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| var typedefType = new TypedefType( | |
| foo, | |
| Nullability.nonNullable, | |
| DartTypeList.empty, | |
| ); | |
| var field = new Field.mutable( | |
| new Name('field'), | |
| type: typedefType, | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingLibrary.addTypedef(foo); | |
| test.enclosingLibrary.addField(field); | |
| return typedefType; | |
| }, | |
| (Node? typedefType) => | |
| "${errorPrefix}The typedef type $typedefType provides 0 type arguments," | |
| " but the typedef declares 1 parameters.", | |
| ); | |
| negative1Test( | |
| 'Dangling typedef reference', | |
| (TestHarness test) { | |
| var foo = new Typedef( | |
| 'Foo', | |
| test.otherRawType, | |
| typeParameters: TypeParameterList.empty, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| var field = new Field.mutable( | |
| new Name('field'), | |
| type: new TypedefType(foo, Nullability.nonNullable, DartTypeList.empty), | |
| isStatic: true, | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| test.enclosingLibrary.addField(field); | |
| return foo; | |
| }, | |
| (Node? foo) => | |
| "${errorPrefix}Dangling reference to '$foo', parent is: 'null'", | |
| ); | |
| negative1Test( | |
| 'Unset bound typedef Foo<T> = dynamic', | |
| (TestHarness test) { | |
| var param = new TypeParameter('T', null); | |
| var foo = new Typedef( | |
| 'Foo', | |
| const DynamicType(), | |
| typeParameters: new TypeParameterList(param), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| param.defaultType = const DynamicType(); | |
| test.addNode(foo); | |
| return foo; | |
| }, | |
| (Node? foo) => | |
| "${errorPrefix}" | |
| "Unset bound on type parameter NominalParameter(Foo.T)", | |
| ); | |
| negative1Test( | |
| 'Unset default type typedef Foo<T> = dynamic', | |
| (TestHarness test) { | |
| var param = new TypeParameter('T', null); | |
| var foo = new Typedef( | |
| 'Foo', | |
| const DynamicType(), | |
| typeParameters: new TypeParameterList(param), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| param.bound = const DynamicType(); | |
| test.addNode(foo); | |
| return foo; | |
| }, | |
| (Node? foo) => | |
| "${errorPrefix}" | |
| "Unset default type on type parameter NominalParameter(Foo.T)", | |
| ); | |
| negative1Test( | |
| 'Non-static top-level field', | |
| (TestHarness test) { | |
| var field = new Field.mutable(new Name('field'), fileUri: dummyUri) | |
| ..fileOffset = dummyFileOffset; | |
| test.enclosingLibrary.addField(field); | |
| return null; | |
| }, | |
| (Node? node) => | |
| "${errorPrefix}The top-level field 'field' should be static", | |
| ); | |
| positiveTest('No library file offset', (TestHarness test) { | |
| var library = new Library(dummyUri, fileUri: dummyUri) | |
| ..parent = test.component; | |
| test.component.libraries.add(library); | |
| return null; | |
| }); | |
| negative1Test('Class file offset', (TestHarness test) { | |
| var cls = new Class(name: 'Class', fileUri: dummyUri); | |
| test.enclosingLibrary.addClass(cls); | |
| return null; | |
| }, (Node? node) => "${errorPrefix}'Class' has no fileOffset"); | |
| negative1Test('Extension file offset', (TestHarness test) { | |
| var extension = new Extension(name: 'Extension', fileUri: dummyUri); | |
| test.enclosingLibrary.addExtension(extension); | |
| return null; | |
| }, (Node? node) => "${errorPrefix}'Extension' has no fileOffset"); | |
| negative1Test('Procedure file offset', (TestHarness test) { | |
| var method = new Procedure( | |
| new Name('method'), | |
| ProcedureKind.Method, | |
| new FunctionNode(null), | |
| fileUri: dummyUri, | |
| ); | |
| test.enclosingClass.addProcedure(method); | |
| return null; | |
| }, (Node? node) => "${errorPrefix}'method' has no fileOffset"); | |
| negative1Test('Field file offset', (TestHarness test) { | |
| var field = new Field.mutable(new Name('field'), fileUri: dummyUri); | |
| test.enclosingClass.addField(field); | |
| return null; | |
| }, (Node? node) => "${errorPrefix}'field' has no fileOffset"); | |
| } | |
| void checkHasError(Target target, Component component, Matcher matcher) { | |
| try { | |
| verifyComponent( | |
| target, | |
| VerificationStage.afterModularTransformations, | |
| component, | |
| ); | |
| } on VerificationError catch (e) { | |
| expect(e.details, matcher); | |
| return; | |
| } | |
| fail('Failed to reject invalid component:\n${componentToString(component)}'); | |
| } | |
| class TestHarness { | |
| late Target target; | |
| late Component component; | |
| late Class objectClass; | |
| late Library stubLibrary; | |
| late TypeParameter classTypeParameter; | |
| late Library enclosingLibrary; | |
| late Class enclosingClass; | |
| late Procedure enclosingMember; | |
| late Class otherClass; | |
| late InterfaceType objectRawType; | |
| late InterfaceType enclosingRawType; | |
| late InterfaceType otherRawType; | |
| void addNode(TreeNode node) { | |
| if (node is Expression) { | |
| addExpression(node); | |
| } else if (node is Statement) { | |
| addStatement(node); | |
| } else if (node is Member) { | |
| addClassMember(node); | |
| } else if (node is Class) { | |
| addClass(node); | |
| } else if (node is Typedef) { | |
| addTypedef(node); | |
| } | |
| } | |
| void addExpression(Expression node) { | |
| addStatement(new ReturnStatement(node)); | |
| } | |
| void addStatement(Statement node) { | |
| var function = enclosingMember.function; | |
| function.body = node..parent = function; | |
| } | |
| void addClassMember(Member node) { | |
| if (node is Procedure) { | |
| enclosingClass.addProcedure(node); | |
| } else if (node is Field) { | |
| enclosingClass.addField(node); | |
| } else if (node is Constructor) { | |
| enclosingClass.addConstructor(node); | |
| } else { | |
| throw "Unexpected class member: ${node.runtimeType}"; | |
| } | |
| } | |
| void addTopLevelMember(Member node) { | |
| if (node is Procedure) { | |
| enclosingLibrary.addProcedure(node); | |
| } else if (node is Field) { | |
| enclosingLibrary.addField(node); | |
| } else { | |
| throw "Unexpected top level member: ${node.runtimeType}"; | |
| } | |
| } | |
| void addClass(Class node) { | |
| enclosingLibrary.addClass(node); | |
| } | |
| void addTypedef(Typedef node) { | |
| enclosingLibrary.addTypedef(node); | |
| } | |
| SyntheticVariable makeVariable() => new SyntheticVariable(); | |
| PositionalParameter makePositionalParameter(String parameterName) => | |
| new PositionalParameter(parameterName: parameterName); | |
| TypeParameter makeTypeParameter([String? name]) { | |
| return new TypeParameter(name, objectRawType, const DynamicType()); | |
| } | |
| new() { | |
| setupComponent(); | |
| } | |
| void setupComponent() { | |
| target = new NoneTarget(new TargetFlags()); | |
| component = new Component(); | |
| Uri dartCoreUri = Uri.parse('dart:core'); | |
| stubLibrary = new Library(dartCoreUri, fileUri: dartCoreUri); | |
| component.libraries.add(stubLibrary..parent = component); | |
| stubLibrary.name = 'dart.core'; | |
| objectClass = new Class(name: 'Object', fileUri: dartCoreUri) | |
| ..fileOffset = dummyFileOffset; | |
| objectRawType = new InterfaceType( | |
| objectClass, | |
| Nullability.nonNullable, | |
| DartTypeList.empty, | |
| ); | |
| stubLibrary.addClass(objectClass); | |
| Uri testUri = Uri.parse('file://test.dart'); | |
| enclosingLibrary = new Library(testUri, fileUri: testUri); | |
| component.libraries.add(enclosingLibrary..parent = component); | |
| enclosingLibrary.name = 'test_lib'; | |
| classTypeParameter = makeTypeParameter('T'); | |
| enclosingClass = new Class( | |
| name: 'TestClass', | |
| typeParameters: new TypeParameterList(classTypeParameter), | |
| supertype: objectClass.asRawSupertype, | |
| fileUri: testUri, | |
| )..fileOffset = dummyFileOffset; | |
| enclosingRawType = new InterfaceType( | |
| enclosingClass, | |
| Nullability.nonNullable, | |
| DartTypeList.dynamic1, | |
| ); | |
| enclosingLibrary.addClass(enclosingClass); | |
| enclosingMember = new Procedure( | |
| new Name('test'), | |
| ProcedureKind.Method, | |
| new FunctionNode(new EmptyStatement()), | |
| fileUri: dummyUri, | |
| )..fileOffset = dummyFileOffset; | |
| enclosingClass.addProcedure(enclosingMember); | |
| otherClass = new Class( | |
| name: 'OtherClass', | |
| typeParameters: new TypeParameterList(makeTypeParameter('OtherT')), | |
| supertype: objectClass.asRawSupertype, | |
| fileUri: testUri, | |
| )..fileOffset = dummyFileOffset; | |
| otherRawType = new InterfaceType( | |
| otherClass, | |
| Nullability.nonNullable, | |
| DartTypeList.dynamic1, | |
| ); | |
| enclosingLibrary.addClass(otherClass); | |
| } | |
| } | |
| void negative1Test( | |
| String name, | |
| Node? Function(TestHarness test) nodeProvider, | |
| dynamic Function(Node? node) matcher, | |
| ) { | |
| TestHarness testHarness = new TestHarness(); | |
| Node? node = nodeProvider(testHarness); | |
| test(name, () { | |
| dynamic matcherResult = matcher(node); | |
| if (matcherResult is String) { | |
| matcherResult = equals(matcherResult); | |
| } | |
| checkHasError(testHarness.target, testHarness.component, matcherResult); | |
| }); | |
| } | |
| void negative2Test( | |
| String name, | |
| List<Node?> Function(TestHarness test) nodeProvider, | |
| dynamic Function(Node? node, Node? other) matcher, | |
| ) { | |
| TestHarness testHarness = new TestHarness(); | |
| List<Node?> nodes = nodeProvider(testHarness); | |
| if (nodes.length != 2) throw "Needs exactly 2 nodes: Node and other!"; | |
| test(name, () { | |
| dynamic matcherResult = matcher(nodes[0], nodes[1]); | |
| if (matcherResult is String) { | |
| matcherResult = equals(matcherResult); | |
| } | |
| checkHasError(testHarness.target, testHarness.component, matcherResult); | |
| }); | |
| } | |
| void simpleNegativeTest( | |
| String name, | |
| dynamic matcher, | |
| void Function(TestHarness test) makeTestCase, | |
| ) { | |
| TestHarness testHarness = new TestHarness(); | |
| test(name, () { | |
| makeTestCase(testHarness); | |
| if (matcher is String) { | |
| matcher = equals(matcher); | |
| } | |
| checkHasError(testHarness.target, testHarness.component, matcher); | |
| }); | |
| } | |
| void positiveTest(String name, void makeTestCase(TestHarness test)) { | |
| test(name, () { | |
| var test = new TestHarness(); | |
| makeTestCase(test); | |
| verifyComponent( | |
| test.target, | |
| VerificationStage.afterModularTransformations, | |
| test.component, | |
| ); | |
| }); | |
| } |