blob: e5232e65b6a2ed67cdcc902cb41251d4c564488f [file] [log] [blame]
// Copyright (c) 2014, 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 'dart:collection';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/parser.dart' show ParserErrorCode;
import 'package:analyzer/src/generated/testing/element_factory.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../src/dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ErrorResolverTest);
defineReflectiveTests(PrefixedNamespaceTest);
defineReflectiveTests(StrictModeTest);
defineReflectiveTests(StrictModeWithoutNullSafetyTest);
defineReflectiveTests(TypePropagationTest);
});
}
@reflectiveTest
class ErrorResolverTest extends PubPackageResolutionTest {
test_breakLabelOnSwitchMember() async {
await assertErrorsInCode(r'''
class A {
void m(int i) {
switch (i) {
l: case 0:
break;
case 1:
break l;
}
}
}''', [
error(CompileTimeErrorCode.BREAK_LABEL_ON_SWITCH_MEMBER, 105, 1),
]);
}
test_continueLabelOnSwitch() async {
await assertErrorsInCode(r'''
class A {
void m(int i) {
l: switch (i) {
case 0:
continue l;
}
}
}''', [
error(CompileTimeErrorCode.CONTINUE_LABEL_ON_SWITCH, 79, 1),
]);
}
test_enclosingElement_invalidLocalFunction() async {
await assertErrorsInCode(r'''
class C {
C() {
int get x => 0;
}
}''', [
error(ParserErrorCode.EXPECTED_TOKEN, 26, 3),
error(HintCode.UNUSED_LOCAL_VARIABLE, 26, 3),
error(HintCode.UNUSED_ELEMENT, 30, 1),
error(ParserErrorCode.MISSING_FUNCTION_PARAMETERS, 32, 2),
]);
var constructor = findElement.unnamedConstructor('C');
var x = findElement.localFunction('x');
expect(x.enclosingElement, constructor);
}
}
@reflectiveTest
class PrefixedNamespaceTest extends PubPackageResolutionTest {
void test_lookup_missing() {
ClassElement element = ElementFactory.classElement2('A');
PrefixedNamespace namespace = PrefixedNamespace('p', _toMap([element]));
expect(namespace.get('p.B'), isNull);
}
void test_lookup_missing_matchesPrefix() {
ClassElement element = ElementFactory.classElement2('A');
PrefixedNamespace namespace = PrefixedNamespace('p', _toMap([element]));
expect(namespace.get('p'), isNull);
}
void test_lookup_valid() {
ClassElement element = ElementFactory.classElement2('A');
PrefixedNamespace namespace = PrefixedNamespace('p', _toMap([element]));
expect(namespace.get('p.A'), same(element));
}
Map<String, Element> _toMap(List<Element> elements) {
Map<String, Element> map = HashMap<String, Element>();
for (Element element in elements) {
map[element.name!] = element;
}
return map;
}
}
/// Instances of the class `StaticTypeVerifier` verify that all of the nodes in
/// an AST structure that should have a static type associated with them do have
/// a static type.
class StaticTypeVerifier extends GeneralizingAstVisitor<void> {
/// A list containing all of the AST Expression nodes that were not resolved.
final List<Expression> _unresolvedExpressions = <Expression>[];
/// The TypeAnnotation nodes that were not resolved.
final List<TypeAnnotation> _unresolvedTypes = <TypeAnnotation>[];
/// Counter for the number of Expression nodes visited that are resolved.
int _resolvedExpressionCount = 0;
/// Counter for the number of TypeName nodes visited that are resolved.
int _resolvedTypeCount = 0;
/// Assert that all of the visited nodes have a static type associated with
/// them.
void assertResolved() {
if (_unresolvedExpressions.isNotEmpty || _unresolvedTypes.isNotEmpty) {
StringBuffer buffer = StringBuffer();
int unresolvedTypeCount = _unresolvedTypes.length;
if (unresolvedTypeCount > 0) {
buffer.write("Failed to resolve ");
buffer.write(unresolvedTypeCount);
buffer.write(" of ");
buffer.write(_resolvedTypeCount + unresolvedTypeCount);
buffer.writeln(" type names:");
for (TypeAnnotation identifier in _unresolvedTypes) {
buffer.write(" ");
buffer.write(identifier.toString());
buffer.write(" (");
buffer.write(_getFileName(identifier));
buffer.write(" : ");
buffer.write(identifier.offset);
buffer.writeln(")");
}
}
int unresolvedExpressionCount = _unresolvedExpressions.length;
if (unresolvedExpressionCount > 0) {
buffer.writeln("Failed to resolve ");
buffer.write(unresolvedExpressionCount);
buffer.write(" of ");
buffer.write(_resolvedExpressionCount + unresolvedExpressionCount);
buffer.writeln(" expressions:");
for (Expression expression in _unresolvedExpressions) {
buffer.write(" ");
buffer.write(expression.toString());
buffer.write(" (");
buffer.write(_getFileName(expression));
buffer.write(" : ");
buffer.write(expression.offset);
buffer.writeln(")");
}
}
fail(buffer.toString());
}
}
@override
void visitBreakStatement(BreakStatement node) {}
@override
void visitCommentReference(CommentReference node) {}
@override
void visitContinueStatement(ContinueStatement node) {}
@override
void visitExportDirective(ExportDirective node) {}
@override
void visitExpression(Expression node) {
node.visitChildren(this);
var staticType = node.staticType;
if (staticType == null) {
_unresolvedExpressions.add(node);
} else {
_resolvedExpressionCount++;
}
}
@override
void visitImportDirective(ImportDirective node) {}
@override
void visitLabel(Label node) {}
@override
void visitLibraryIdentifier(LibraryIdentifier node) {}
@override
void visitNamedType(NamedType node) {
// Note: do not visit children from this node, the child SimpleIdentifier in
// TypeName (i.e. "String") does not have a static type defined.
// TODO(brianwilkerson) Not visiting the children means that we won't catch
// type arguments that were not resolved.
if (node.type == null) {
_unresolvedTypes.add(node);
} else {
_resolvedTypeCount++;
}
}
@override
void visitPrefixedIdentifier(PrefixedIdentifier node) {
// In cases where we have a prefixed identifier where the prefix is dynamic,
// we don't want to assert that the node will have a type.
if (node.staticType == null && node.prefix.typeOrThrow.isDynamic) {
return;
}
super.visitPrefixedIdentifier(node);
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
// In cases where identifiers are being used for something other than an
// expressions, then they can be ignored.
var parent = node.parent;
if (parent is MethodInvocation && identical(node, parent.methodName)) {
return;
} else if (parent is RedirectingConstructorInvocation &&
identical(node, parent.constructorName)) {
return;
} else if (parent is SuperConstructorInvocation &&
identical(node, parent.constructorName)) {
return;
} else if (parent is ConstructorName && identical(node, parent.name)) {
return;
} else if (parent is ConstructorFieldInitializer &&
identical(node, parent.fieldName)) {
return;
} else if (node.staticElement is PrefixElement) {
// Prefixes don't have a type.
return;
}
super.visitSimpleIdentifier(node);
}
@override
void visitTypeAnnotation(TypeAnnotation node) {
if (node.type == null) {
_unresolvedTypes.add(node);
} else {
_resolvedTypeCount++;
}
super.visitTypeAnnotation(node);
}
String _getFileName(AstNode? node) {
// TODO (jwren) there are two copies of this method, one here and one in
// ResolutionVerifier, they should be resolved into a single method
if (node != null) {
AstNode root = node.root;
if (root is CompilationUnit) {
CompilationUnit rootCU = root;
if (rootCU.declaredElement != null) {
return rootCU.declaredElement!.source.fullName;
} else {
return "<unknown file- CompilationUnit.getElement() returned null>";
}
} else {
return "<unknown file- CompilationUnit.getRoot() is not a CompilationUnit>";
}
}
return "<unknown file- ASTNode is null>";
}
}
/// The class `StrictModeTest` contains tests to ensure that the correct errors
/// and warnings are reported when the analysis engine is run in strict mode.
@reflectiveTest
class StrictModeTest extends PubPackageResolutionTest with StrictModeTestCases {
test_conditional_isNot() async {
await assertNoErrorsInCode(r'''
int f(num n) {
return (n is! int) ? 0 : n & 0x0F;
}
''');
}
test_conditional_or_is() async {
await assertNoErrorsInCode(r'''
int f(num n) {
return (n is! int || n < 0) ? 0 : n & 0x0F;
}
''');
}
test_if_isNot() async {
await assertNoErrorsInCode(r'''
int f(num n) {
if (n is! int) {
return 0;
} else {
return n & 0x0F;
}
}
''');
}
test_if_isNot_abrupt() async {
await assertNoErrorsInCode(r'''
int f(num n) {
if (n is! int) {
return 0;
}
return n & 0x0F;
}
''');
}
test_if_or_is() async {
await assertNoErrorsInCode(r'''
int f(num n) {
if (n is! int || n < 0) {
return 0;
} else {
return n & 0x0F;
}
}
''');
}
}
mixin StrictModeTestCases on PubPackageResolutionTest {
test_assert_is() async {
await assertErrorsInCode(r'''
int f(num n) {
assert (n is int);
return n & 0x0F;
}''', [
error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 47, 1),
]);
}
test_conditional_and_is() async {
await assertNoErrorsInCode(r'''
int f(num n) {
return (n is int && n > 0) ? n & 0x0F : 0;
}''');
}
test_conditional_is() async {
await assertNoErrorsInCode(r'''
int f(num n) {
return (n is int) ? n & 0x0F : 0;
}''');
}
test_for() async {
await assertNoErrorsInCode(r'''
void f(List<int> list) {
num sum = 0; // ignore: unused_local_variable
for (int i = 0; i < list.length; i++) {
sum += list[i];
}
}
''');
}
test_forEach() async {
await assertErrorsInCode(r'''
void f(List<int> list) {
num sum = 0; // ignore: unused_local_variable
for (num n in list) {
sum += n & 0x0F;
}
}
''', [
error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 110, 1),
]);
}
test_if_and_is() async {
await assertNoErrorsInCode(r'''
int f(num n) {
if (n is int && n > 0) {
return n & 0x0F;
}
return 0;
}''');
}
test_if_is() async {
await assertNoErrorsInCode(r'''
int f(num n) {
if (n is int) {
return n & 0x0F;
}
return 0;
}''');
}
test_localVar() async {
await assertErrorsInCode(r'''
int f() {
num n = 1234;
return n & 0x0F;
}''', [
error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 37, 1),
]);
}
}
/// The class `StrictModeTest` contains tests to ensure that the correct errors
/// and warnings are reported when the analysis engine is run in strict mode.
@reflectiveTest
class StrictModeWithoutNullSafetyTest extends PubPackageResolutionTest
with StrictModeTestCases, WithoutNullSafetyMixin {
test_conditional_isNot() async {
await assertErrorsInCode(r'''
int f(num n) {
return (n is! int) ? 0 : n & 0x0F;
}
''', [
error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 44, 1),
]);
}
test_conditional_or_is() async {
await assertErrorsInCode(r'''
int f(num n) {
return (n is! int || n < 0) ? 0 : n & 0x0F;
}
''', [
error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 53, 1),
]);
}
test_if_isNot() async {
await assertErrorsInCode(r'''
int f(num n) {
if (n is! int) {
return 0;
} else {
return n & 0x0F;
}
}
''', [
error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 72, 1),
]);
}
test_if_isNot_abrupt() async {
await assertErrorsInCode(r'''
int f(num n) {
if (n is! int) {
return 0;
}
return n & 0x0F;
}
''', [
error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 63, 1),
]);
}
test_if_or_is() async {
await assertErrorsInCode(r'''
int f(num n) {
if (n is! int || n < 0) {
return 0;
} else {
return n & 0x0F;
}
}
''', [
error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 81, 1),
]);
}
}
@reflectiveTest
class TypePropagationTest extends PubPackageResolutionTest {
test_assignment_null() async {
String code = r'''
main() {
int v; // declare
v = null;
return v; // return
}''';
await resolveTestCode(code);
assertType(findElement.localVar('v').type, 'int');
assertTypeNull(findNode.simple('v; // declare'));
assertType(findNode.simple('v; // return'), 'int');
}
test_initializer_hasStaticType() async {
await resolveTestCode(r'''
f() {
int v = 0;
return v;
}''');
assertType(findElement.localVar('v').type, 'int');
assertTypeNull(findNode.simple('v = 0;'));
assertType(findNode.simple('v;'), 'int');
}
test_initializer_hasStaticType_parameterized() async {
await resolveTestCode(r'''
f() {
List<int> v = <int>[];
return v;
}''');
assertType(findElement.localVar('v').type, 'List<int>');
assertTypeNull(findNode.simple('v ='));
assertType(findNode.simple('v;'), 'List<int>');
}
test_initializer_null() async {
await resolveTestCode(r'''
main() {
int v = null;
return v;
}''');
assertType(findElement.localVar('v').type, 'int');
assertTypeNull(findNode.simple('v ='));
assertType(findNode.simple('v;'), 'int');
}
test_invocation_target_prefixed() async {
newFile2('$testPackageLibPath/a.dart', r'''
int max(int x, int y) => 0;
''');
await resolveTestCode('''
import 'a.dart' as helper;
main() {
helper.max(10, 10); // marker
}''');
assertElement(
findNode.simple('max(10, 10)'),
findElement.importFind('package:test/a.dart').topFunction('max'),
);
}
test_is_subclass() async {
await resolveTestCode(r'''
class A {}
class B extends A {
B m() => this;
}
A f(A p) {
if (p is B) {
return p.m();
}
return p;
}''');
assertElement(
findNode.methodInvocation('p.m()'),
findElement.method('m', of: 'B'),
);
}
test_mutatedOutsideScope() async {
// https://code.google.com/p/dart/issues/detail?id=22732
await assertNoErrorsInCode(r'''
class Base {
}
class Derived extends Base {
get y => null;
}
class C {
void f(Base x) {
x = Base();
if (x is Derived) {
print(x.y); // BAD
}
x = Base();
}
}
void g(Base x) {
x = Base();
if (x is Derived) {
print(x.y); // GOOD
}
x = Base();
}
''');
}
test_objectAccessInference_disabled_for_library_prefix() async {
newFile2('$testPackageLibPath/a.dart', '''
dynamic get hashCode => 42;
''');
await assertNoErrorsInCode('''
import 'a.dart' as helper;
main() {
helper.hashCode;
}''');
assertTypeDynamic(findNode.prefixed('helper.hashCode'));
}
test_objectAccessInference_disabled_for_local_getter() async {
await assertNoErrorsInCode('''
dynamic get hashCode => null;
main() {
hashCode; // marker
}''');
assertTypeDynamic(findNode.simple('hashCode; // marker'));
}
test_objectMethodInference_disabled_for_library_prefix() async {
newFile2('$testPackageLibPath/a.dart', '''
dynamic toString = (int x) => x + 42;
''');
await assertNoErrorsInCode('''
import 'a.dart' as helper;
main() {
helper.toString();
}''');
assertTypeDynamic(
findNode.functionExpressionInvocation('helper.toString()'),
);
}
test_objectMethodInference_disabled_for_local_function() async {
await resolveTestCode('''
main() {
dynamic toString = () => null;
toString(); // marker
}''');
assertTypeDynamic(findElement.localVar('toString').type);
assertTypeNull(findNode.simple('toString ='));
assertTypeDynamic(findNode.simple('toString(); // marker'));
}
@failingTest
test_propagatedReturnType_functionExpression() async {
// TODO(scheglov) disabled because we don't resolve function expression
await resolveTestCode(r'''
main() {
var v = (() {return 42;})();
}''');
assertTypeDynamic(findNode.simple('v = '));
}
}