| // Copyright (c) 2017, 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:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/test_utilities/function_ast_visitor.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../../dart/resolution/context_collection_resolution.dart'; |
| import '../../dart/resolution/node_text_expectations.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(Dart2InferenceTest); |
| defineReflectiveTests(UpdateNodeTextExpectations); |
| }); |
| } |
| |
| /// Tests for Dart2 inference rules back-ported from FrontEnd. |
| /// |
| /// https://github.com/dart-lang/sdk/issues/31638 |
| @reflectiveTest |
| class Dart2InferenceTest extends PubPackageResolutionTest { |
| test_assertInitializer() async { |
| await assertNoErrorsInCode(r''' |
| T foo<T>(int _) => throw 0; |
| |
| class C { |
| C() : assert(foo(0), foo(1)); |
| } |
| '''); |
| |
| var node = findNode.singleAssertInitializer; |
| assertResolvedNodeText(node, r''' |
| AssertInitializer |
| assertKeyword: assert |
| leftParenthesis: ( |
| condition: MethodInvocation |
| methodName: SimpleIdentifier |
| token: foo |
| element: <testLibrary>::@function::foo |
| staticType: T Function<T>(int) |
| argumentList: ArgumentList |
| leftParenthesis: ( |
| arguments |
| IntegerLiteral |
| literal: 0 |
| correspondingParameter: ParameterMember |
| baseElement: <testLibraryFragment>::@function::foo::@parameter::_#element |
| substitution: {T: bool} |
| staticType: int |
| rightParenthesis: ) |
| staticInvokeType: bool Function(int) |
| staticType: bool |
| typeArgumentTypes |
| bool |
| comma: , |
| message: MethodInvocation |
| methodName: SimpleIdentifier |
| token: foo |
| element: <testLibrary>::@function::foo |
| staticType: T Function<T>(int) |
| argumentList: ArgumentList |
| leftParenthesis: ( |
| arguments |
| IntegerLiteral |
| literal: 1 |
| correspondingParameter: ParameterMember |
| baseElement: <testLibraryFragment>::@function::foo::@parameter::_#element |
| substitution: {T: dynamic} |
| staticType: int |
| rightParenthesis: ) |
| staticInvokeType: dynamic Function(int) |
| staticType: dynamic |
| typeArgumentTypes |
| dynamic |
| rightParenthesis: ) |
| '''); |
| } |
| |
| test_assertStatement_message() async { |
| await assertNoErrorsInCode(r''' |
| T foo<T>(int _) => throw 0; |
| |
| void f() { |
| assert(foo(0), foo(1)); |
| } |
| '''); |
| |
| var node = findNode.singleAssertStatement; |
| assertResolvedNodeText(node, r''' |
| AssertStatement |
| assertKeyword: assert |
| leftParenthesis: ( |
| condition: MethodInvocation |
| methodName: SimpleIdentifier |
| token: foo |
| element: <testLibrary>::@function::foo |
| staticType: T Function<T>(int) |
| argumentList: ArgumentList |
| leftParenthesis: ( |
| arguments |
| IntegerLiteral |
| literal: 0 |
| correspondingParameter: ParameterMember |
| baseElement: <testLibraryFragment>::@function::foo::@parameter::_#element |
| substitution: {T: bool} |
| staticType: int |
| rightParenthesis: ) |
| staticInvokeType: bool Function(int) |
| staticType: bool |
| typeArgumentTypes |
| bool |
| comma: , |
| message: MethodInvocation |
| methodName: SimpleIdentifier |
| token: foo |
| element: <testLibrary>::@function::foo |
| staticType: T Function<T>(int) |
| argumentList: ArgumentList |
| leftParenthesis: ( |
| arguments |
| IntegerLiteral |
| literal: 1 |
| correspondingParameter: ParameterMember |
| baseElement: <testLibraryFragment>::@function::foo::@parameter::_#element |
| substitution: {T: dynamic} |
| staticType: int |
| rightParenthesis: ) |
| staticInvokeType: dynamic Function(int) |
| staticType: dynamic |
| typeArgumentTypes |
| dynamic |
| rightParenthesis: ) |
| semicolon: ; |
| '''); |
| } |
| |
| test_closure_downwardReturnType_arrow() async { |
| var code = r''' |
| void main() { |
| List<int> Function() g; |
| g = () => 42; |
| } |
| '''; |
| await resolveTestCode(code); |
| Expression closure = findNode.expression('() => 42'); |
| assertType(closure, 'List<int> Function()'); |
| } |
| |
| test_closure_downwardReturnType_block() async { |
| var code = r''' |
| void main() { |
| List<int> Function() g; |
| g = () { // mark |
| return 42; |
| }; |
| } |
| '''; |
| await resolveTestCode(code); |
| Expression closure = findNode.expression('() { // mark'); |
| assertType(closure, 'List<int> Function()'); |
| } |
| |
| test_compoundAssignment_simpleIdentifier_topLevel() async { |
| await assertErrorsInCode( |
| r''' |
| class A {} |
| |
| class B extends A { |
| B operator +(int i) => this; |
| } |
| |
| B get topLevel => new B(); |
| |
| void set topLevel(A value) {} |
| |
| main() { |
| var /*@type=B*/ v = topLevel += 1; |
| } |
| ''', |
| [error(WarningCode.UNUSED_LOCAL_VARIABLE, 152, 1)], |
| ); |
| _assertTypeAnnotations(); |
| } |
| |
| test_forIn_identifier() async { |
| var code = r''' |
| T f<T>() => null; |
| |
| class A {} |
| |
| A aTopLevel; |
| void set aTopLevelSetter(A value) {} |
| |
| class C { |
| A aField; |
| void set aSetter(A value) {} |
| void test() { |
| A aLocal; |
| for (aLocal in f()) {} // local |
| for (aField in f()) {} // field |
| for (aSetter in f()) {} // setter |
| for (aTopLevel in f()) {} // top variable |
| for (aTopLevelSetter in f()) {} // top setter |
| } |
| }'''; |
| await resolveTestCode(code); |
| void assertInvocationType(String prefix) { |
| var invocation = findNode.methodInvocation(prefix); |
| assertType(invocation, 'Iterable<A>'); |
| } |
| |
| assertInvocationType('f()) {} // local'); |
| assertInvocationType('f()) {} // field'); |
| assertInvocationType('f()) {} // setter'); |
| assertInvocationType('f()) {} // top variable'); |
| assertInvocationType('f()) {} // top setter'); |
| } |
| |
| test_forIn_variable_implicitlyTyped() async { |
| var code = r''' |
| class A {} |
| class B extends A {} |
| |
| List<T> f<T extends A>(List<T> items) => items; |
| |
| void test(List<A> listA, List<B> listB) { |
| for (var a1 in f(listA)) {} // 1 |
| for (A a2 in f(listA)) {} // 2 |
| for (var b1 in f(listB)) {} // 3 |
| for (A b2 in f(listB)) {} // 4 |
| for (B b3 in f(listB)) {} // 5 |
| } |
| '''; |
| await resolveTestCode(code); |
| void assertTypes( |
| String vSearch, |
| String vType, |
| String fSearch, |
| String fType, |
| ) { |
| var node = findNode.declaredIdentifier(vSearch); |
| |
| var element = node.declaredElement2 as LocalVariableElement; |
| assertType(element.type, vType); |
| |
| var invocation = findNode.methodInvocation(fSearch); |
| assertType(invocation, fType); |
| } |
| |
| assertTypes('a1 in', 'A', 'f(listA)) {} // 1', 'List<A>'); |
| assertTypes('a2 in', 'A', 'f(listA)) {} // 2', 'List<A>'); |
| assertTypes('b1 in', 'B', 'f(listB)) {} // 3', 'List<B>'); |
| assertTypes('b2 in', 'A', 'f(listB)) {} // 4', 'List<A>'); |
| assertTypes('b3 in', 'B', 'f(listB)) {} // 5', 'List<B>'); |
| } |
| |
| test_implicitVoidReturnType_default() async { |
| var code = r''' |
| class C { |
| set x(_) {} |
| operator []=(int index, double value) => null; |
| } |
| '''; |
| await resolveTestCode(code); |
| ClassElement c = findElement2.class_('C'); |
| |
| SetterElement x = c.setters2[0]; |
| expect(x.returnType, VoidTypeImpl.instance); |
| |
| MethodElement operator = c.methods2[0]; |
| expect(operator.displayName, '[]='); |
| expect(operator.returnType, VoidTypeImpl.instance); |
| } |
| |
| test_implicitVoidReturnType_derived() async { |
| var code = r''' |
| class Base { |
| dynamic set x(_) {} |
| dynamic operator[]=(int x, int y) => null; |
| } |
| class Derived extends Base { |
| set x(_) {} |
| operator[]=(int x, int y) {} |
| }'''; |
| await resolveTestCode(code); |
| ClassElement c = findElement2.class_('Derived'); |
| |
| SetterElement x = c.setters2[0]; |
| expect(x.returnType, VoidTypeImpl.instance); |
| |
| MethodElement operator = c.methods2[0]; |
| expect(operator.displayName, '[]='); |
| expect(operator.returnType, VoidTypeImpl.instance); |
| } |
| |
| test_listMap_empty() async { |
| var code = r''' |
| var x = []; |
| var y = {}; |
| '''; |
| await resolveTestCode(code); |
| var xNode = findNode.variableDeclaration('x = '); |
| var xfragment = xNode.declaredFragment!; |
| assertType(xfragment.element.type, 'List<dynamic>'); |
| |
| var yNode = findNode.variableDeclaration('y = '); |
| var yfragment = yNode.declaredFragment!; |
| assertType(yfragment.element.type, 'Map<dynamic, dynamic>'); |
| } |
| |
| test_listMap_null() async { |
| var code = r''' |
| var x = [null]; |
| var y = {null: null}; |
| '''; |
| await resolveTestCode(code); |
| var xNode = findNode.variableDeclaration('x = '); |
| var xFragment = xNode.declaredFragment!; |
| assertType(xFragment.element.type, 'List<Null>'); |
| |
| var yNode = findNode.variableDeclaration('y = '); |
| var yFragment = yNode.declaredFragment!; |
| assertType(yFragment.element.type, 'Map<Null, Null>'); |
| } |
| |
| test_logicalAnd() async { |
| await assertNoErrorsInCode(r''' |
| T foo<T>() => throw 0; |
| |
| void f() { |
| foo() && foo(); |
| } |
| '''); |
| |
| var node = findNode.singleBinaryExpression; |
| assertResolvedNodeText(node, r''' |
| BinaryExpression |
| leftOperand: MethodInvocation |
| methodName: SimpleIdentifier |
| token: foo |
| element: <testLibrary>::@function::foo |
| staticType: T Function<T>() |
| argumentList: ArgumentList |
| leftParenthesis: ( |
| rightParenthesis: ) |
| staticInvokeType: bool Function() |
| staticType: bool |
| typeArgumentTypes |
| bool |
| operator: && |
| rightOperand: MethodInvocation |
| methodName: SimpleIdentifier |
| token: foo |
| element: <testLibrary>::@function::foo |
| staticType: T Function<T>() |
| argumentList: ArgumentList |
| leftParenthesis: ( |
| rightParenthesis: ) |
| correspondingParameter: <null> |
| staticInvokeType: bool Function() |
| staticType: bool |
| typeArgumentTypes |
| bool |
| element: <null> |
| staticInvokeType: null |
| staticType: bool |
| '''); |
| } |
| |
| test_logicalOr() async { |
| await assertNoErrorsInCode(r''' |
| T foo<T>() => throw 0; |
| |
| void f() { |
| foo() || foo(); |
| } |
| '''); |
| |
| var node = findNode.singleBinaryExpression; |
| assertResolvedNodeText(node, r''' |
| BinaryExpression |
| leftOperand: MethodInvocation |
| methodName: SimpleIdentifier |
| token: foo |
| element: <testLibrary>::@function::foo |
| staticType: T Function<T>() |
| argumentList: ArgumentList |
| leftParenthesis: ( |
| rightParenthesis: ) |
| staticInvokeType: bool Function() |
| staticType: bool |
| typeArgumentTypes |
| bool |
| operator: || |
| rightOperand: MethodInvocation |
| methodName: SimpleIdentifier |
| token: foo |
| element: <testLibrary>::@function::foo |
| staticType: T Function<T>() |
| argumentList: ArgumentList |
| leftParenthesis: ( |
| rightParenthesis: ) |
| correspondingParameter: <null> |
| staticInvokeType: bool Function() |
| staticType: bool |
| typeArgumentTypes |
| bool |
| element: <null> |
| staticInvokeType: null |
| staticType: bool |
| '''); |
| } |
| |
| test_switchExpression_asContext_forCases() async { |
| var code = r''' |
| class C<T> { |
| const C(); |
| } |
| |
| void test(C<int> x) { |
| switch (x) { |
| case const C(): |
| break; |
| default: |
| break; |
| } |
| }'''; |
| await resolveTestCode(code); |
| var node = findNode.instanceCreation('C():'); |
| assertType(node, 'C<int>'); |
| } |
| |
| test_switchExpression_asContext_forCases_language219() async { |
| var code = r''' |
| // @dart = 2.19 |
| class C<T> { |
| const C(); |
| } |
| |
| void test(C<int> x) { |
| switch (x) { |
| case const C(): |
| break; |
| default: |
| break; |
| } |
| }'''; |
| await resolveTestCode(code); |
| var node = findNode.instanceCreation('const C():'); |
| assertType(node, 'C<int>'); |
| } |
| |
| test_voidType_method() async { |
| var code = r''' |
| class C { |
| void m() {} |
| } |
| var x = new C().m(); |
| main() { |
| var y = new C().m(); |
| } |
| '''; |
| await resolveTestCode(code); |
| var xNode = findNode.variableDeclaration('x = '); |
| var xFragment = xNode.declaredFragment!; |
| expect(xFragment.element.type, VoidTypeImpl.instance); |
| |
| var yNode = findNode.variableDeclaration('y = '); |
| var yFragment = yNode.declaredFragment!; |
| expect(yFragment.element.type, VoidTypeImpl.instance); |
| } |
| |
| test_voidType_topLevelFunction() async { |
| var code = r''' |
| void f() {} |
| var x = f(); |
| main() { |
| var y = f(); |
| } |
| '''; |
| await resolveTestCode(code); |
| var xNode = findNode.variableDeclaration('x = '); |
| var xFragment = xNode.declaredFragment!; |
| expect(xFragment.element.type, VoidTypeImpl.instance); |
| |
| var yNode = findNode.variableDeclaration('y = '); |
| var yFragment = yNode.declaredFragment!; |
| expect(yFragment.element.type, VoidTypeImpl.instance); |
| } |
| |
| void _assertTypeAnnotations() { |
| var code = result.content; |
| var unit = result.unit; |
| |
| var types = <int, String>{}; |
| { |
| int lastIndex = 0; |
| while (true) { |
| const prefix = '/*@type='; |
| int openIndex = code.indexOf(prefix, lastIndex); |
| if (openIndex == -1) { |
| break; |
| } |
| int closeIndex = code.indexOf('*/', openIndex + 1); |
| expect(closeIndex, isPositive); |
| types[openIndex] = code.substring( |
| openIndex + prefix.length, |
| closeIndex, |
| ); |
| lastIndex = closeIndex; |
| } |
| } |
| |
| unit.accept( |
| FunctionAstVisitor( |
| simpleIdentifier: (node) { |
| var comment = node.token.precedingComments; |
| if (comment != null) { |
| var expectedType = types[comment.offset]; |
| if (expectedType != null) { |
| var element = node.element as VariableElement; |
| String actualType = typeString(element.type); |
| expect(actualType, expectedType, reason: '@${comment.offset}'); |
| } |
| } |
| }, |
| ), |
| ); |
| } |
| } |