blob: f917e72d3462d4af0a8596527e9b892d68192d6a [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:analysis_server/src/nullability/conditional_discard.dart';
import 'package:analysis_server/src/nullability/decorated_type.dart';
import 'package:analysis_server/src/nullability/expression_checks.dart';
import 'package:analysis_server/src/nullability/transitional_api.dart';
import 'package:analysis_server/src/nullability/unit_propagation.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/source.dart';
/// Visitor that gathers constraint variables for nullability migration from
/// code to be migrated.
///
/// The return type of each `visit...` method is a [DecoratedType] indicating
/// the static type of the element declared by the visited node, along with the
/// constraint variables that will determine its nullability. For `visit...`
/// methods that don't visit declarations, `null` will be returned.
class ConstraintVariableGatherer extends GeneralizingAstVisitor<DecoratedType> {
/// Constraint variables and decorated types are stored here.
final VariableRecorder _variables;
/// The file being analyzed.
final Source _source;
/// If the parameters of a function or method are being visited, the
/// [DecoratedType] of the corresponding function or method type.
///
/// TODO(paulberry): should this be updated when we visit generic function
/// type syntax? How about when we visit old-style function-typed formal
/// parameters?
DecoratedType _currentFunctionType;
final bool _permissive;
final NullabilityMigrationAssumptions assumptions;
ConstraintVariableGatherer(
this._variables, this._source, this._permissive, this.assumptions);
/// Creates and stores a [DecoratedType] object corresponding to the given
/// [type] AST, and returns it.
DecoratedType decorateType(TypeAnnotation type) {
return type == null
// TODO(danrubel): Return something other than this
// to indicate that we should insert a type for the declaration
// that is missing a type reference.
? new DecoratedType(DynamicTypeImpl.instance, ConstraintVariable.always)
: type.accept(this);
}
@override
DecoratedType visitDefaultFormalParameter(DefaultFormalParameter node) {
var decoratedType = node.parameter.accept(this);
ConstraintVariable optional;
if (node.declaredElement.hasRequired) {
optional = null;
} else if (node.defaultValue != null) {
optional = ConstraintVariable.always;
} else {
optional = decoratedType.nullable;
_variables.recordPossiblyOptional(_source, node, optional);
}
if (optional != null) {
_currentFunctionType
.namedParameterOptionalVariables[node.declaredElement.name] =
optional;
}
return null;
}
@override
DecoratedType visitFormalParameter(FormalParameter node) {
// Do not visit children
// TODO(paulberry): handle all types of formal parameters
// - NormalFormalParameter
// - SimpleFormalParameter
// - FieldFormalParameter
// - FunctionTypedFormalParameter
// - DefaultFormalParameter
return null;
}
@override
DecoratedType visitFunctionDeclaration(FunctionDeclaration node) {
_handleExecutableDeclaration(node.declaredElement, node.returnType,
node.functionExpression.parameters);
return null;
}
@override
DecoratedType visitMethodDeclaration(MethodDeclaration node) {
_handleExecutableDeclaration(
node.declaredElement, node.returnType, node.parameters);
return null;
}
@override
DecoratedType visitNode(AstNode node) {
if (_permissive) {
try {
return super.visitNode(node);
} catch (_) {
return null;
}
} else {
return super.visitNode(node);
}
}
@override
DecoratedType visitSimpleFormalParameter(SimpleFormalParameter node) {
var type = decorateType(node.type);
var declaredElement = node.declaredElement;
assert(type.nonNullIntent == null);
type.nonNullIntent = NonNullIntent(node.offset);
_variables.recordDecoratedElementType(declaredElement, type);
if (declaredElement.isNamed) {
_currentFunctionType.namedParameters[declaredElement.name] = type;
} else {
_currentFunctionType.positionalParameters.add(type);
}
return type;
}
@override
DecoratedType visitTypeAnnotation(TypeAnnotation node) {
assert(node != null); // TODO(paulberry)
assert(node is NamedType); // TODO(paulberry)
var type = node.type;
if (type.isVoid) return DecoratedType(type, ConstraintVariable.always);
assert(
type is InterfaceType || type is TypeParameterType); // TODO(paulberry)
var typeArguments = const <DecoratedType>[];
if (type is InterfaceType && type.typeParameters.isNotEmpty) {
if (node is TypeName) {
assert(node.typeArguments != null);
typeArguments =
node.typeArguments.arguments.map((t) => t.accept(this)).toList();
} else {
assert(false); // TODO(paulberry): is this possible?
}
}
var nullable = node.question == null
? TypeIsNullable(node.end)
: ConstraintVariable.always;
var decoratedType = DecoratedTypeAnnotation(type, nullable, node.end,
typeArguments: typeArguments);
_variables.recordDecoratedTypeAnnotation(_source, node, decoratedType);
return decoratedType;
}
@override
DecoratedType visitTypeName(TypeName node) => visitTypeAnnotation(node);
/// Common handling of function and method declarations.
void _handleExecutableDeclaration(ExecutableElement declaredElement,
TypeAnnotation returnType, FormalParameterList parameters) {
var decoratedReturnType = decorateType(returnType);
var previousFunctionType = _currentFunctionType;
// TODO(paulberry): test that it's correct to use `null` for the nullability
// of the function type
var functionType = DecoratedType(declaredElement.type, null,
returnType: decoratedReturnType,
positionalParameters: [],
namedParameters: {},
namedParameterOptionalVariables: {});
_currentFunctionType = functionType;
try {
parameters.accept(this);
} finally {
_currentFunctionType = previousFunctionType;
}
_variables.recordDecoratedElementType(declaredElement, functionType);
}
}
/// Repository of constraint variables and decorated types corresponding to the
/// code being migrated.
///
/// This data structure records the results of the first pass of migration
/// ([ConstraintVariableGatherer], which finds all the variables that need to be
/// constrained).
abstract class VariableRecorder {
/// Associates decorated type information with the given [element].
void recordDecoratedElementType(Element element, DecoratedType type);
/// Associates decorated type information with the given [type] node.
void recordDecoratedTypeAnnotation(
Source source, TypeAnnotation node, DecoratedTypeAnnotation type);
/// Associates a constraint variable with the question of whether the given
/// named parameter should be optional (should not have a `required`
/// annotation added to it).
void recordPossiblyOptional(Source source, DefaultFormalParameter parameter,
ConstraintVariable variable);
}
/// Repository of constraint variables and decorated types corresponding to the
/// code being migrated.
///
/// This data structure allows the second pass of migration
/// ([ConstraintGatherer], which builds all the constraints) to access the
/// results of the first ([ConstraintVariableGatherer], which finds all the
/// variables that need to be constrained).
abstract class VariableRepository {
/// Retrieves the [DecoratedType] associated with the static type of the given
/// [element].
///
/// If [create] is `true`, and no decorated type is found for the given
/// element, one is synthesized using [DecoratedType.forElement].
DecoratedType decoratedElementType(Element element, {bool create: false});
/// Records conditional discard information for the given AST node (which is
/// an `if` statement or a conditional (`?:`) expression).
void recordConditionalDiscard(
Source source, AstNode node, ConditionalDiscard conditionalDiscard);
/// Associates decorated type information with the given [element].
///
/// TODO(paulberry): why is this in both [VariableRecorder] and
/// [VariableRepository]?
void recordDecoratedElementType(Element element, DecoratedType type);
/// Associates decorated type information with the given expression [node].
void recordDecoratedExpressionType(Expression node, DecoratedType type);
/// Associates a set of nullability checks with the given expression [node].
void recordExpressionChecks(
Source source, Expression expression, ExpressionChecks checks);
}