blob: 070647419745570dd8f48ec2d7b22301a5c91f45 [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/ast/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/error/return_type_verifier.dart';
import 'package:analyzer/src/generated/error_verifier.dart';
import 'package:collection/collection.dart';
/// Reports on invalid functions passed as error handlers.
///
/// Functions must either accept exactly one positional parameter, or exactly
/// two positional parameters. The one parameter (or the first parameter) must
/// have a type of `dynamic`, `Object`, or `Object?`. If a second parameter is
/// accepted, it must have a type of `StackTrace`.
///
/// A function is checked if it is passed as:
/// * as the first argument to [Future.catchError],
/// * as the 'onError' named argument to [Future.then],
/// * as the first argument to [Stream.handleError],
/// * as the 'onError' named argument to [Future.onError],
/// * as the first argument to [StreamSubscription.onError],
///
/// Additionally, a function passed as the first argument to
/// [Future<T>.catchError] must return `FutureOr<T>`, and any return statements in a
/// function literal must return a value of type `FutureOr<T>`.
class ErrorHandlerVerifier {
final ErrorReporter _errorReporter;
final TypeProviderImpl _typeProvider;
final TypeSystemImpl _typeSystem;
final ReturnTypeVerifier _returnTypeVerifier;
ErrorHandlerVerifier(
this._errorReporter, this._typeProvider, this._typeSystem)
: _returnTypeVerifier = ReturnTypeVerifier(
typeProvider: _typeProvider,
typeSystem: _typeSystem,
errorReporter: _errorReporter,
);
void verifyMethodInvocation(MethodInvocation node) {
var target = node.realTarget;
if (target == null) {
return;
}
if (node.argumentList.arguments.isEmpty) {
return;
}
var targetType = target.staticType;
if (targetType == null) {
return;
}
var methodName = node.methodName.name;
if (methodName == 'catchError' && targetType.isDartAsyncFuture) {
var callback = node.argumentList.arguments.first;
if (callback is NamedExpression) {
// This implies that no positional arguments are passed.
return;
}
_checkFutureCatchErrorOnError(target, callback);
return;
}
if (methodName == 'then' && targetType.isDartAsyncFuture) {
var callback = node.argumentList.arguments
.whereType<NamedExpression>()
.firstWhereOrNull(
(argument) => argument.name.label.name == 'onError');
if (callback == null) {
return;
}
var callbackType = callback.staticType;
if (callbackType == null) {
return;
}
if (callbackType is FunctionType) {
// TODO(srawlins): Also check return type of the 'onError' named
// argument to [Future<T>.then].
_checkErrorHandlerFunctionType(
callback, callbackType, _typeProvider.voidType,
checkFirstParameterType: callback.expression is FunctionExpression);
return;
}
// [callbackType] might be dart:core's Function, or something not
// assignable to Function, in which case an error is reported elsewhere.
}
if (methodName == 'handleError' &&
_isDartCoreAsyncType(targetType, 'Stream')) {
var callback = node.argumentList.arguments.first;
if (callback is NamedExpression) {
// This implies that no positional arguments are passed.
return;
}
var callbackType = callback.staticType;
if (callbackType == null) {
return;
}
if (callbackType is FunctionType) {
_checkErrorHandlerFunctionType(
callback, callbackType, _typeProvider.voidType,
checkFirstParameterType: callback is FunctionExpression);
return;
}
// [callbackType] might be dart:core's Function, or something not
// assignable to Function, in which case an error is reported elsewhere.
}
if (methodName == 'listen' && _isDartCoreAsyncType(targetType, 'Stream')) {
var callback = node.argumentList.arguments
.whereType<NamedExpression>()
.firstWhereOrNull(
(argument) => argument.name.label.name == 'onError');
if (callback == null) {
return;
}
var callbackType = callback.staticType;
if (callbackType == null) {
return;
}
if (callbackType is FunctionType) {
_checkErrorHandlerFunctionType(
callback, callbackType, _typeProvider.voidType,
checkFirstParameterType: callback.expression is FunctionExpression);
return;
}
// [callbackType] might be dart:core's Function, or something not
// assignable to Function, in which case an error is reported elsewhere.
}
if (methodName == 'onError' &&
_isDartCoreAsyncType(targetType, 'StreamSubscription')) {
var callback = node.argumentList.arguments.first;
if (callback is NamedExpression) {
// This implies that no positional arguments are passed.
return;
}
var callbackType = callback.staticType;
if (callbackType == null) {
return;
}
if (callbackType is FunctionType) {
_checkErrorHandlerFunctionType(
callback, callbackType, _typeProvider.voidType,
checkFirstParameterType: callback is FunctionExpression);
return;
}
// [callbackType] might be dart:core's Function, or something not
// assignable to Function, in which case an error is reported elsewhere.
}
}
/// Checks that [expression], a function with static type [expressionType], is
/// a valid error handler.
///
/// Only checks the first parameter type if [checkFirstParameterType] is true.
/// Certain error handlers are allowed to specify a different type for their
/// first parameter.
void _checkErrorHandlerFunctionType(Expression expression,
FunctionType expressionType, DartType expectedFunctionReturnType,
{bool checkFirstParameterType = true}) {
void report() {
_errorReporter.reportErrorForNode(
HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE_TO_ERROR_HANDLER,
expression,
[expressionType, expectedFunctionReturnType],
);
}
var parameters = expressionType.parameters;
if (parameters.isEmpty) {
return report();
}
var firstParameter = parameters.first;
if (firstParameter.isNamed) {
return report();
} else if (checkFirstParameterType) {
if (!_typeSystem.isSubtypeOf(
_typeProvider.objectType, firstParameter.type)) {
return report();
}
}
if (parameters.length == 2) {
var secondParameter = parameters[1];
if (secondParameter.isNamed) {
return report();
} else {
if (!_typeSystem.isSubtypeOf(
_typeProvider.stackTraceType, secondParameter.type)) {
return report();
}
}
} else if (parameters.length > 2) {
return report();
}
}
/// Check the 'onError' argument given to [Future.catchError].
void _checkFutureCatchErrorOnError(Expression target, Expression callback) {
var targetType = target.staticType as InterfaceType;
var targetFutureType = targetType.typeArguments.first;
var expectedReturnType = _typeProvider.futureOrType(targetFutureType);
if (callback is FunctionExpression) {
// TODO(migration): should be FunctionType, not nullable
var callbackType = callback.staticType as FunctionType;
_checkErrorHandlerFunctionType(
callback, callbackType, expectedReturnType);
var catchErrorOnErrorExecutable = EnclosingExecutableContext(
callback.declaredElement,
isAsynchronous: true,
catchErrorOnErrorReturnType: expectedReturnType);
var returnStatementVerifier =
_ReturnStatementVerifier(_returnTypeVerifier);
_returnTypeVerifier.enclosingExecutable = catchErrorOnErrorExecutable;
callback.body.accept(returnStatementVerifier);
} else {
var callbackType = callback.staticType;
if (callbackType is FunctionType) {
_checkReturnType(expectedReturnType, callbackType.returnType, callback);
_checkErrorHandlerFunctionType(
callback, callbackType, expectedReturnType);
} else {
// If [callback] is not even a Function, then ErrorVerifier will have
// reported this.
}
}
}
void _checkReturnType(
DartType expectedType, DartType functionReturnType, Expression callback) {
if (!_typeSystem.isAssignableTo(functionReturnType, expectedType)) {
_errorReporter.reportErrorForNode(
HintCode.RETURN_TYPE_INVALID_FOR_CATCH_ERROR,
callback,
[functionReturnType, expectedType],
);
}
}
/// Returns whether [element] represents the []
bool _isDartCoreAsyncType(DartType type, String typeName) =>
type is InterfaceType &&
type.element.name == typeName &&
type.element.library.isDartAsync;
}
/// Visits a function body, looking for return statements.
class _ReturnStatementVerifier extends RecursiveAstVisitor<void> {
final ReturnTypeVerifier _returnTypeVerifier;
_ReturnStatementVerifier(this._returnTypeVerifier);
@override
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
_returnTypeVerifier.verifyExpressionFunctionBody(node);
super.visitExpressionFunctionBody(node);
}
@override
void visitFunctionExpression(FunctionExpression node) {
// Do not visit within [node]. We have no interest in return statements
// within.
}
@override
void visitReturnStatement(ReturnStatement node) {
_returnTypeVerifier.verifyReturnStatement(node);
super.visitReturnStatement(node);
}
}