blob: cff0b7b61ee5286143b8e92cb0932fb9b787e428 [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/generated/source.dart';
import 'package:nnbd_migration/instrumentation.dart';
/// Edge origin resulting from a type in already-migrated code.
///
/// For example, in the Map class in dart:core:
/// V? operator [](Object key);
///
/// this class is used for the edge connecting `always` to the return type of
/// `operator []`, due to the fact that dart:core has already been migrated and
/// the type is explicitly nullable.
///
/// Note that since a single element can have a complex type, it is likely that
/// multiple edges will be created with an [AlreadyMigratedTypeOrigin] pointing
/// to the same type. To distinguish which edge corresponds to which part of
/// the element's type, use the callbacks
/// [NullabilityMigrationInstrumentation.externalDecoratedType] and
/// [NullabilityMigrationInstrumentation.externalDecoratedTypeParameterBound].
class AlreadyMigratedTypeOrigin extends EdgeOrigin {
/// Indicates whether the already-migrated type is nullable or not.
final bool isNullable;
AlreadyMigratedTypeOrigin.forElement(Element element, this.isNullable)
: super.forElement(element);
@override
String get description => '${isNullable ? 'nullable' : 'non-nullable'}'
' type in already-migrated code';
@override
EdgeOriginKind get kind => EdgeOriginKind.alreadyMigratedType;
}
/// Edge origin resulting from the use of a type that is always nullable.
///
/// For example, in the following code snippet:
/// void f(dynamic x) {}
///
/// this class is used for the edge connecting `always` to the type of f's `x`
/// parameter, due to the fact that the `dynamic` type is always considered
/// nullable.
class AlwaysNullableTypeOrigin extends EdgeOrigin {
/// Indicates whether the always-nullable type is the `void` type (if `false`,
/// it is the `dynamic` type).
final bool isVoid;
AlwaysNullableTypeOrigin(Source source, AstNode node, this.isVoid)
: super(source, node);
AlwaysNullableTypeOrigin.forElement(Element element, this.isVoid)
: super.forElement(element);
@override
String get description =>
'${isVoid ? 'void' : 'dynamic'} type is nullable by definition';
@override
EdgeOriginKind get kind => EdgeOriginKind.alwaysNullableType;
}
/// Edge origin resulting from the presence of a call to
/// `ArgumentError.checkNotNull`.
///
/// For example, in the following code snippet:
/// void f(int i) {
/// ArgumentError.checkNotNull(i);
/// }
///
/// this class is used for the edge connecting the type of f's `i` parameter to
/// `never`, due to the `checkNotNull` call proclaiming that `i` is not `null`.
class ArgumentErrorCheckNotNullOrigin extends EdgeOrigin {
ArgumentErrorCheckNotNullOrigin(Source source, SimpleIdentifier node)
: super(source, node);
@override
String get description => 'value checked to be non-null';
@override
EdgeOriginKind get kind => EdgeOriginKind.argumentErrorCheckNotNull;
}
/// An edge origin used for edges that originated because of a tear-off of
/// `call` on a function type.
class CallTearOffOrigin extends EdgeOrigin {
CallTearOffOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'tear-off of .call';
@override
EdgeOriginKind get kind => EdgeOriginKind.callTearOff;
}
/// Edge origin resulting from the use of a value on the LHS of a compound
/// assignment.
class CompoundAssignmentOrigin extends EdgeOrigin {
CompoundAssignmentOrigin(Source source, AssignmentExpression node)
: super(source, node);
@override
String get description => 'compound assignment';
@override
EdgeOriginKind get kind => EdgeOriginKind.compoundAssignment;
@override
AssignmentExpression get node => super.node as AssignmentExpression;
}
/// Edge origin resulting from the use of an element which does not affect the
/// nullability graph in other ways.
class DummyOrigin extends EdgeOrigin {
DummyOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'dummy';
@override
EdgeOriginKind get kind => EdgeOriginKind.dummy;
}
/// An edge origin used for edges that originated because of an assignment
/// involving a value with a dynamic type.
class DynamicAssignmentOrigin extends EdgeOrigin {
DynamicAssignmentOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'assignment of dynamic value';
@override
EdgeOriginKind get kind => EdgeOriginKind.dynamicAssignment;
}
/// Common interface for classes providing information about how an edge came
/// to be; that is, what was found in the source code that led the migration
/// tool to create the edge.
abstract class EdgeOrigin extends EdgeOriginInfo {
@override
final Source source;
@override
final AstNode node;
@override
final Element element;
EdgeOrigin(this.source, this.node) : element = null;
EdgeOrigin.forElement(this.element)
: source = null,
node = null;
/// Retrieves the location in the source code that caused this edge to be
/// created, or `null` if unknown.
CodeReference get codeReference {
if (node != null) {
return CodeReference.fromAstNode(node);
}
return null;
}
/// User-friendly description of the edge.
String get description;
}
/// An edge origin used for edges that originated because of a reference to an
/// enum value, which cannot be null.
class EnumValueOrigin extends EdgeOrigin {
EnumValueOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'non-nullable enum value';
@override
EdgeOriginKind get kind => EdgeOriginKind.enumValue;
}
/// Edge origin resulting from the relationship between a field formal parameter
/// and the corresponding field.
class FieldFormalParameterOrigin extends EdgeOrigin {
FieldFormalParameterOrigin(Source source, FieldFormalParameter node)
: super(source, node);
@override
String get description => 'field formal parameter';
@override
EdgeOriginKind get kind => EdgeOriginKind.fieldFormalParameter;
}
/// An edge origin used for edges that originated because a field was not
/// initialized.
///
/// The AST node associated with the edge is the AST node for the constructor
/// that failed to initialize the field (or the class, if the constructor is
/// synthetic).
class FieldNotInitializedOrigin extends EdgeOrigin {
FieldNotInitializedOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'field not initialized';
@override
EdgeOriginKind get kind => EdgeOriginKind.fieldNotInitialized;
}
/// Edge origin resulting from the use of an iterable type in a for-each loop.
///
/// For example, in the following code snippet:
/// void f(Iterable<int> l) {
/// for (int i in l) {}
/// }
///
/// this class is used for the edge connecting the type of `l`'s `int` type
/// parameter to the type of `i`.
class ForEachVariableOrigin extends EdgeOrigin {
ForEachVariableOrigin(Source source, ForEachParts node) : super(source, node);
@override
String get description => 'variable in "for each" loop';
@override
EdgeOriginKind get kind => EdgeOriginKind.forEachVariable;
}
/// Edge origin resulting from the relationship between a getter and a setter.
class GetterSetterCorrespondenceOrigin extends EdgeOrigin {
GetterSetterCorrespondenceOrigin(Source source, AstNode node)
: super(source, node);
@override
String get description => 'getter/setter correspondence';
@override
EdgeOriginKind get kind => EdgeOriginKind.getterSetterCorrespondence;
}
/// Edge origin resulting from the use of greatest lower bound.
///
/// For example, in the following code snippet:
/// void Function(int) f(void Function(int) x, void Function(int) y)
/// => x ?? y;
///
/// the `int` in the return type is nullable if both the `int`s in the types of
/// `x` and `y` are nullable, due to the fact that the `int` in the return type
/// is the greatest lower bound of the two other `int`s.
class GreatestLowerBoundOrigin extends EdgeOrigin {
GreatestLowerBoundOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'greatest lower bound';
@override
EdgeOriginKind get kind => EdgeOriginKind.greatestLowerBound;
}
/// Edge origin resulting from the presence of a `??` operator.
class IfNullOrigin extends EdgeOrigin {
IfNullOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'if-null operator';
@override
EdgeOriginKind get kind => EdgeOriginKind.ifNull;
}
/// Edge origin resulting from the implicit call from a mixin application
/// constructor to the corresponding super constructor.
///
/// For example, in the following code snippet:
/// class C {
/// C(int i);
/// }
/// mixin M {}
/// class D = C with M;
///
/// this class is used for the edge connecting the types of the `i` parameters
/// between the implicit constructor for `D` and the explicit constructor for
/// `C`.
class ImplicitMixinSuperCallOrigin extends EdgeOrigin {
ImplicitMixinSuperCallOrigin(Source source, ClassTypeAlias node)
: super(source, node);
@override
String get description => 'implicit super call in mixin constructor';
@override
EdgeOriginKind get kind => EdgeOriginKind.implicitMixinSuperCall;
}
/// Edge origin resulting from the implicit assignment of `null` to a top level
/// variable or field that lacks an initializer.
class ImplicitNullInitializerOrigin extends EdgeOrigin {
ImplicitNullInitializerOrigin(Source source, AstNode node)
: super(source, node);
@override
String get description => 'uninitialized variable';
@override
EdgeOriginKind get kind => EdgeOriginKind.implicitNullInitializer;
}
/// Edge origin resulting from a `return;` statement which implicitly returns
/// `null`.
class ImplicitNullReturnOrigin extends EdgeOrigin {
ImplicitNullReturnOrigin(Source source, ReturnStatement node)
: super(source, node);
@override
String get description => 'implicit return of null';
@override
EdgeOriginKind get kind => EdgeOriginKind.implicitNullReturn;
@override
ReturnStatement get node => super.node as ReturnStatement;
}
/// Edge origin used for edges that arise from an implicit use of `this`, e.g.
/// during a method call from an extension.
class ImplicitThisOrigin extends EdgeOrigin {
ImplicitThisOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'implicit use of `this`';
@override
EdgeOriginKind get kind => EdgeOriginKind.implicitThis;
}
/// Edge origin resulting from the inference of a type parameter, which
/// can affects the nullability of that type parameter's bound.
class InferredTypeParameterInstantiationOrigin extends EdgeOrigin {
InferredTypeParameterInstantiationOrigin(Source source, AstNode node)
: super(source, node);
@override
String get description => 'inferred type parameter';
@override
EdgeOriginKind get kind => EdgeOriginKind.inferredTypeParameterInstantiation;
}
/// An edge origin used for edges that originated because of an instance
/// creation expression.
class InstanceCreationOrigin extends EdgeOrigin {
InstanceCreationOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'instance creation';
@override
EdgeOriginKind get kind => EdgeOriginKind.instanceCreation;
}
/// Edge origin resulting from a class that is instantiated to bounds.
///
/// For example, in the following code snippet:
/// class C<T extends Object> {}
/// C x;
///
/// this class is used for the edge connecting the type of x's type parameter
/// with the type bound in the declaration of C.
class InstantiateToBoundsOrigin extends EdgeOrigin {
InstantiateToBoundsOrigin(Source source, TypeName node) : super(source, node);
@override
String get description => 'type instantiated to bounds';
@override
EdgeOriginKind get kind => EdgeOriginKind.instantiateToBounds;
}
/// Edge origin resulting from the use of a type as the main type in an 'is'
/// check.
///
/// Before the migration, there was no way to say `is int?`, and therefore,
/// `is int` should migrate to non-null int.
class IsCheckMainTypeOrigin extends EdgeOrigin {
IsCheckMainTypeOrigin(Source source, TypeAnnotation node)
: super(source, node);
@override
String get description => '"is" check does not accept null';
@override
EdgeOriginKind get kind => EdgeOriginKind.isCheckMainType;
}
/// An edge origin used for the return type of an iterator method that might be
/// changed into an extension method from package:collection.
class IteratorMethodReturnOrigin extends EdgeOrigin {
IteratorMethodReturnOrigin(Source source, AstNode node) : super(source, node);
@override
String get description =>
'Call to iterator method with orElse that returns null';
@override
EdgeOriginKind get kind => EdgeOriginKind.iteratorMethodReturn;
}
/// An edge origin used for the type argument of a list constructor that
/// specified an initial length, because that type argument must be nullable.
class ListLengthConstructorOrigin extends EdgeOrigin {
ListLengthConstructorOrigin(Source source, AstNode node)
: super(source, node);
@override
String get description => 'construction of list via a length';
@override
EdgeOriginKind get kind => EdgeOriginKind.listLengthConstructor;
}
/// An edge origin used for edges that originated because a literal expression
/// has a known nullability.
class LiteralOrigin extends EdgeOrigin {
LiteralOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'literal expression';
@override
EdgeOriginKind get kind => EdgeOriginKind.literal;
}
/// Edge origin resulting from a call site that does not supply a named
/// parameter.
///
/// For example, in the following code snippet:
/// void f({int i}) {}
/// main() {
/// f();
/// }
///
/// this class is used for the edge connecting `always` to the type of f's `i`
/// parameter, due to the fact that the call to `f` implicitly passes a null
/// value for `i`.
class NamedParameterNotSuppliedOrigin extends EdgeOrigin {
NamedParameterNotSuppliedOrigin(Source source, AstNode node)
: super(source, node);
@override
String get description => 'named parameter not supplied';
@override
EdgeOriginKind get kind => EdgeOriginKind.namedParameterNotSupplied;
}
/// Edge origin for the nullability of an expression that whose type is fixed by
/// the language definition to be non-nullable `bool`.
class NonNullableBoolTypeOrigin extends EdgeOrigin {
NonNullableBoolTypeOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'non-null boolean expression';
@override
EdgeOriginKind get kind => EdgeOriginKind.nonNullableBoolType;
}
/// Edge origin resulting from the class/superclass relationship for a class
/// whose superclass is implicitly `Object`.
class NonNullableObjectSuperclass extends EdgeOrigin {
NonNullableObjectSuperclass(Source source, AstNode node)
: super(source, node);
@override
String get description => 'implicit supertype of Object';
@override
EdgeOriginKind get kind => EdgeOriginKind.nonNullableObjectSuperclass;
}
/// Edge origin resulting from the usage of a value in a circumstance that
/// requires it to be non-nullable
class NonNullableUsageOrigin extends EdgeOrigin {
NonNullableUsageOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'value cannot be null';
@override
EdgeOriginKind get kind => EdgeOriginKind.nonNullableUsage;
}
/// Edge origin resulting from the presence of a non-null assertion.
///
/// For example, in the following code snippet:
/// void f(int i) {
/// assert(i != null);
/// }
///
/// this class is used for the edge connecting the type of f's `i` parameter to
/// `never`, due to the assert statement proclaiming that `i` is not `null`.
class NonNullAssertionOrigin extends EdgeOrigin {
NonNullAssertionOrigin(Source source, Assertion node) : super(source, node);
@override
String get description => 'value asserted to be non-null';
@override
EdgeOriginKind get kind => EdgeOriginKind.nonNullAssertion;
}
/// Edge origin resulting from the presence of an explicit nullability hint
/// comment.
///
/// For example, in the following code snippet:
/// void f(int/*?*/ i) {}
///
/// this class is used for the edge connecting `always` to the type of f's `i`
/// parameter, due to the presence of the `/*?*/` comment.
class NullabilityCommentOrigin extends EdgeOrigin {
/// Indicates whether the nullability comment makes the type nullable or
/// non-nullable.
final bool isNullable;
NullabilityCommentOrigin(Source source, AstNode node, this.isNullable)
: assert(node is TypeAnnotation ||
node is FunctionTypedFormalParameter ||
(node is FieldFormalParameter && node.parameters != null)),
super(source, node);
@override
String get description =>
'explicitly hinted to be ${isNullable ? 'nullable' : 'non-nullable'}';
@override
EdgeOriginKind get kind => EdgeOriginKind.nullabilityComment;
}
/// Edge origin resulting from the presence of an optional formal parameter.
///
/// For example, in the following code snippet:
/// void f({int i}) {}
///
/// this class is used for the edge connecting `always` to the type of f's `i`
/// parameter, due to the fact that `i` is optional and has no initializer.
class OptionalFormalParameterOrigin extends EdgeOrigin {
OptionalFormalParameterOrigin(Source source, DefaultFormalParameter node)
: super(source, node);
@override
String get description => 'optional formal parameter must be nullable';
@override
EdgeOriginKind get kind => EdgeOriginKind.optionalFormalParameter;
}
/// Edge origin resulting from an inheritance relationship between two method
/// parameters.
class ParameterInheritanceOrigin extends EdgeOrigin {
ParameterInheritanceOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'function parameter override';
@override
EdgeOriginKind get kind => EdgeOriginKind.parameterInheritance;
}
/// Edge origin resulting from the presence of a call to quiver's
/// `checkNotNull`.
///
/// For example, in the following code snippet:
/// import 'package:quiver/check.dart';
/// void f(int i) {
/// checkNotNull(i);
/// }
///
/// this class is used for the edge connecting the type of f's `i` parameter to
/// `never`, due to the `checkNotNull` call proclaiming that `i` is not `null`.
class QuiverCheckNotNullOrigin extends EdgeOrigin {
QuiverCheckNotNullOrigin(Source source, SimpleIdentifier node)
: super(source, node);
@override
String get description => 'value checked to be non-null';
@override
EdgeOriginKind get kind => EdgeOriginKind.quiverCheckNotNull;
}
/// Edge origin resulting from an inheritance relationship between two method
/// return types.
class ReturnTypeInheritanceOrigin extends EdgeOrigin {
ReturnTypeInheritanceOrigin(Source source, AstNode node)
: super(source, node);
@override
String get description => 'function return type override';
@override
EdgeOriginKind get kind => EdgeOriginKind.returnTypeInheritance;
}
/// Edge origin resulting from the use of a stacktrace parameter in a catch
/// directive. The type of such parameters is fixed by the language as
/// non-nullable `StackTrace`.
class StackTraceTypeOrigin extends EdgeOrigin {
StackTraceTypeOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'stack trace variable is nullable';
@override
EdgeOriginKind get kind => EdgeOriginKind.stackTraceTypeOrigin;
}
/// Edge origin resulting from the use of `this` or `super`.
class ThisOrSuperOrigin extends EdgeOrigin {
/// Indicates whether the expression in question is `this`. If `false`, the
/// expression in question is `super`.
final bool isThis;
ThisOrSuperOrigin(Source source, AstNode node, this.isThis)
: super(source, node);
@override
String get description =>
'type of "${isThis ? 'this' : 'super'}" is non-nullable';
@override
EdgeOriginKind get kind => EdgeOriginKind.thisOrSuper;
}
/// An edge origin used for edges that originated from the type of a `throw` or
/// `rethrow`.
class ThrowOrigin extends EdgeOrigin {
ThrowOrigin(Source source, AstNode node) : super(source, node);
@override
String get description =>
'type of thrown expression is presumed non-nullable';
@override
EdgeOriginKind get kind => EdgeOriginKind.throw_;
}
/// Edge origin resulting from a usage of a typedef.
///
/// Since typedefs require multiple phases to resolve, they are represented by
/// a set of inferred nodes. In the secondary phases of graph build, those get
/// unioned with references to the nodes referring to source code. The origin of
/// those union edges will be [TypedefReferenceOrigin].
class TypedefReferenceOrigin extends EdgeOrigin {
TypedefReferenceOrigin(Source source, TypeName node) : super(source, node);
@override
String get description => 'reference to typedef';
@override
EdgeOriginKind get kind => EdgeOriginKind.typedefReference;
}
/// Edge origin resulting from the instantiation of a type parameter, which
/// affects the nullability of that type parameter's bound.
class TypeParameterInstantiationOrigin extends EdgeOrigin {
TypeParameterInstantiationOrigin(Source source, TypeAnnotation node)
: super(source, node);
@override
String get description => 'type parameter instantiation';
@override
EdgeOriginKind get kind => EdgeOriginKind.typeParameterInstantiation;
@override
TypeAnnotation get node => super.node as TypeAnnotation;
}
/// Edge origin resulting from the read of a variable that has not been
/// definitely assigned a value.
class UninitializedReadOrigin extends EdgeOrigin {
UninitializedReadOrigin(Source source, AstNode node) : super(source, node);
@override
String get description => 'local variable might not be initialized';
@override
EdgeOriginKind get kind => EdgeOriginKind.uninitializedRead;
}