blob: bf9d88c1be362d74b4c26b3e87320ea7461d2788 [file] [log] [blame]
// 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.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:meta/meta.dart';
import 'package:nnbd_migration/src/conditional_discard.dart';
import 'package:nnbd_migration/src/decorated_type.dart';
import 'package:nnbd_migration/src/edge_builder.dart';
import 'package:nnbd_migration/src/expression_checks.dart';
import 'package:nnbd_migration/src/node_builder.dart';
import 'package:nnbd_migration/src/nullability_node.dart';
import 'package:nnbd_migration/src/variables.dart';
import 'package:test/test.dart';
import 'abstract_single_unit.dart';
/// Mixin allowing unit tests to create decorated types easily.
mixin DecoratedTypeTester implements DecoratedTypeTesterBase {
int _offset = 0;
Map<TypeParameterElement, DecoratedType> _decoratedTypeParameterBounds =
Map.identity();
NullabilityNode get always => graph.always;
DecoratedType get bottom => DecoratedType(typeProvider.bottomType, never);
DecoratedType get dynamic_ => DecoratedType(typeProvider.dynamicType, always);
NullabilityNode get never => graph.never;
DecoratedType get null_ => DecoratedType(typeProvider.nullType, always);
DecoratedType get void_ => DecoratedType(typeProvider.voidType, always);
DecoratedType function(DecoratedType returnType,
{List<DecoratedType> required = const [],
List<DecoratedType> positional = const [],
Map<String, DecoratedType> named = const {},
List<TypeParameterElement> typeFormals = const [],
NullabilityNode node}) {
int i = 0;
var parameters = required
.map((t) => ParameterElementImpl.synthetic(
'p${i++}', t.type, ParameterKind.REQUIRED))
.toList();
parameters.addAll(positional.map((t) => ParameterElementImpl.synthetic(
'p${i++}', t.type, ParameterKind.POSITIONAL)));
parameters.addAll(named.entries.map((e) => ParameterElementImpl.synthetic(
e.key, e.value.type, ParameterKind.NAMED)));
return DecoratedType(
FunctionTypeImpl.synthetic(returnType.type, typeFormals, parameters),
node ?? newNode(),
typeFormalBounds: typeFormals
.map((formal) => _decoratedTypeParameterBounds[formal])
.toList(),
returnType: returnType,
positionalParameters: required.toList()..addAll(positional),
namedParameters: named);
}
DecoratedType int_({NullabilityNode node}) =>
DecoratedType(typeProvider.intType, node ?? newNode());
DecoratedType list(DecoratedType elementType, {NullabilityNode node}) =>
DecoratedType(typeProvider.listType.instantiate([elementType.type]),
node ?? newNode(),
typeArguments: [elementType]);
NullabilityNode newNode() => NullabilityNode.forTypeAnnotation(_offset++);
DecoratedType object({NullabilityNode node}) =>
DecoratedType(typeProvider.objectType, node ?? newNode());
TypeParameterElement typeParameter(String name, DecoratedType bound) {
var element = TypeParameterElementImpl.synthetic(name);
element.bound = bound.type;
_decoratedTypeParameterBounds[element] = bound;
return element;
}
DecoratedType typeParameterType(TypeParameterElement typeParameter,
{NullabilityNode node}) =>
DecoratedType(typeParameter.type, node ?? newNode());
}
/// Base functionality that must be implemented by classes mixing in
/// [DecoratedTypeTester].
abstract class DecoratedTypeTesterBase {
NullabilityGraph get graph;
TypeProvider get typeProvider;
}
/// Mixin allowing unit tests to check for the presence of graph edges.
mixin EdgeTester {
NullabilityGraphForTesting get graph;
NullabilityEdge assertEdge(
NullabilityNode source, NullabilityNode destination,
{@required bool hard, List<NullabilityNode> guards = const []}) {
var edges = getEdges(source, destination);
if (edges.length == 0) {
fail('Expected edge $source -> $destination, found none');
} else if (edges.length != 1) {
fail('Found multiple edges $source -> $destination');
} else {
var edge = edges[0];
expect(edge.hard, hard);
expect(edge.guards, unorderedEquals(guards));
return edge;
}
}
void assertNoEdge(NullabilityNode source, NullabilityNode destination) {
var edges = getEdges(source, destination);
if (edges.isNotEmpty) {
fail('Expected no edge $source -> $destination, found ${edges.length}');
}
}
void assertUnion(NullabilityNode x, NullabilityNode y) {
var edges = getEdges(x, y);
for (var edge in edges) {
if (edge.isUnion) {
expect(edge.sources, hasLength(1));
return;
}
}
fail('Expected union between $x and $y, not found');
}
List<NullabilityEdge> getEdges(
NullabilityNode source, NullabilityNode destination) =>
graph
.getUpstreamEdges(destination)
.where((e) => e.primarySource == source)
.toList();
}
/// Mock representation of constraint variables.
class InstrumentedVariables extends Variables {
final _conditionalDiscard = <AstNode, ConditionalDiscard>{};
final _decoratedExpressionTypes = <Expression, DecoratedType>{};
final _expressionChecks = <Expression, ExpressionChecks>{};
final _possiblyOptional = <DefaultFormalParameter, NullabilityNode>{};
InstrumentedVariables(NullabilityGraph graph) : super(graph);
/// Gets the [ExpressionChecks] associated with the given [expression].
ExpressionChecks checkExpression(Expression expression) =>
_expressionChecks[_normalizeExpression(expression)];
/// Gets the [conditionalDiscard] associated with the given [expression].
ConditionalDiscard conditionalDiscard(AstNode node) =>
_conditionalDiscard[node];
/// Gets the [DecoratedType] associated with the given [expression].
DecoratedType decoratedExpressionType(Expression expression) =>
_decoratedExpressionTypes[_normalizeExpression(expression)];
/// Gets the [NullabilityNode] associated with the possibility that
/// [parameter] may be optional.
NullabilityNode possiblyOptionalParameter(DefaultFormalParameter parameter) =>
_possiblyOptional[parameter];
@override
void recordConditionalDiscard(
Source source, AstNode node, ConditionalDiscard conditionalDiscard) {
_conditionalDiscard[node] = conditionalDiscard;
super.recordConditionalDiscard(source, node, conditionalDiscard);
}
void recordDecoratedExpressionType(Expression node, DecoratedType type) {
super.recordDecoratedExpressionType(node, type);
_decoratedExpressionTypes[_normalizeExpression(node)] = type;
}
@override
void recordExpressionChecks(
Source source, Expression expression, ExpressionChecks checks) {
super.recordExpressionChecks(source, expression, checks);
_expressionChecks[_normalizeExpression(expression)] = checks;
}
@override
void recordPossiblyOptional(
Source source, DefaultFormalParameter parameter, NullabilityNode node) {
_possiblyOptional[parameter] = node;
super.recordPossiblyOptional(source, parameter, node);
}
/// Unwraps any parentheses surrounding [expression].
Expression _normalizeExpression(Expression expression) {
while (expression is ParenthesizedExpression) {
expression = (expression as ParenthesizedExpression).expression;
}
return expression;
}
}
class EdgeBuilderTestBase extends MigrationVisitorTestBase {
/// Analyzes the given source code, producing constraint variables and
/// constraints for it.
@override
Future<CompilationUnit> analyze(String code) async {
var unit = await super.analyze(code);
unit.accept(EdgeBuilder(
typeProvider, typeSystem, variables, graph, testSource, null));
return unit;
}
}
class MigrationVisitorTestBase extends AbstractSingleUnitTest with EdgeTester {
final InstrumentedVariables variables;
final NullabilityGraphForTesting graph;
MigrationVisitorTestBase() : this._(NullabilityGraphForTesting());
MigrationVisitorTestBase._(this.graph)
: variables = InstrumentedVariables(graph);
NullabilityNode get always => graph.always;
NullabilityNode get never => graph.never;
TypeProvider get typeProvider => testAnalysisResult.typeProvider;
TypeSystem get typeSystem => testAnalysisResult.typeSystem;
Future<CompilationUnit> analyze(String code) async {
await resolveTestUnit(code);
testUnit
.accept(NodeBuilder(variables, testSource, null, graph, typeProvider));
return testUnit;
}
/// Gets the [DecoratedType] associated with the constructor declaration whose
/// name matches [search].
DecoratedType decoratedConstructorDeclaration(String search) => variables
.decoratedElementType(findNode.constructor(search).declaredElement);
Map<ClassElement, DecoratedType> decoratedDirectSupertypes(String name) {
return variables.decoratedDirectSupertypes(findElement.classOrMixin(name));
}
/// Gets the [DecoratedType] associated with the generic function type
/// annotation whose text is [text].
DecoratedType decoratedGenericFunctionTypeAnnotation(String text) {
return variables.decoratedTypeAnnotation(
testSource, findNode.genericFunctionType(text));
}
/// Gets the [DecoratedType] associated with the method declaration whose
/// name matches [search].
DecoratedType decoratedMethodType(String search) => variables
.decoratedElementType(findNode.methodDeclaration(search).declaredElement);
/// Gets the [DecoratedType] associated with the type annotation whose text
/// is [text].
DecoratedType decoratedTypeAnnotation(String text) {
return variables.decoratedTypeAnnotation(
testSource, findNode.typeAnnotation(text));
}
NullabilityNode possiblyOptionalParameter(String text) {
return variables.possiblyOptionalParameter(findNode.defaultParameter(text));
}
/// Gets the [ConditionalDiscard] information associated with the statement
/// whose text is [text].
ConditionalDiscard statementDiscard(String text) {
return variables.conditionalDiscard(findNode.statement(text));
}
}