blob: a0308f1d2559a49b51dfddd9762c9a85fedb38f8 [file] [log] [blame]
// Copyright (c) 2021, 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/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/error/hint_codes.dart';
/// Verifies usages of `Future.value` and `Completer.complete` when null-safety
/// is enabled.
///
/// `Future.value` and `Completer.complete` both accept a `FutureOr<T>?` as an
/// optional argument but throw an exception when `T` is non-nullable and `null`
/// is passed as an argument.
///
/// This verifier detects and reports those scenarios.
class NullSafeApiVerifier {
final ErrorReporter _errorReporter;
final TypeSystemImpl _typeSystem;
NullSafeApiVerifier(this._errorReporter, this._typeSystem);
/// Reports an error if the expression creates a `Future<T>.value` with a non-
/// nullable value `T` and an argument that is effectively `null`.
void instanceCreation(InstanceCreationExpression expression) {
if (!_typeSystem.isNonNullableByDefault) return;
final constructor = expression.constructorName.staticElement;
if (constructor == null) return;
final type = constructor.returnType;
final isFutureValue = type.isDartAsyncFuture && constructor.name == 'value';
if (isFutureValue) {
_checkTypes(expression, 'Future.value', type.typeArguments.single,
expression.argumentList);
}
}
/// Reports an error if `Completer<T>.complete` is invoked with a non-nullable
/// `T` and an argument that is effectively `null`.
void methodInvocation(MethodInvocation node) {
if (!_typeSystem.isNonNullableByDefault) return;
final targetType = node.realTarget?.staticType;
final targetClass = targetType?.element;
if (targetClass == null || targetType is! InterfaceType) return;
if (targetClass.library?.isDartAsync == true &&
targetClass.name == 'Completer' &&
node.methodName.name == 'complete') {
_checkTypes(node, 'Completer.complete', targetType.typeArguments.single,
node.argumentList);
}
}
void _checkTypes(
Expression node, String memberName, DartType type, ArgumentList args) {
// If there's more than one argument, something else is wrong (and will
// generate another diagnostic). Also, only check the argument type if we
// expect a non-nullable type in the first place.
if (args.arguments.length > 1 || !_typeSystem.isNonNullable(type)) return;
final argument = args.arguments.isEmpty ? null : args.arguments.single;
final argumentType = argument?.staticType;
// Skip if the type is not currently resolved.
if (argument != null && argumentType == null) return;
final argumentIsNull =
argument == null || _typeSystem.isNull(argumentType!);
if (argumentIsNull) {
_errorReporter.reportErrorForNode(
HintCode.NULL_ARGUMENT_TO_NON_NULL_TYPE,
argument ?? node,
[memberName, type.getDisplayString(withNullability: true)]);
}
}
}