blob: 42a6dc4011a2d63e48d93901226c65ecb49a3f7d [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/ast/extensions.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';
/// Reports on invalid functions passed to [Future.catchError].
class CatchErrorVerifier {
final ErrorReporter _errorReporter;
final TypeProviderImpl _typeProvider;
final TypeSystemImpl _typeSystem;
final ReturnTypeVerifier _returnTypeVerifier;
CatchErrorVerifier(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;
}
var methodName = node.methodName;
if (!(methodName.name == 'catchError' &&
target.typeOrThrow.isDartAsyncFuture)) {
return;
}
if (node.argumentList.arguments.isEmpty) {
return;
}
var callback = node.argumentList.arguments.first;
if (callback is NamedExpression) {
// This implies that no positional arguments are passed.
return;
}
var targetType = target.staticType as InterfaceType;
var targetFutureType = targetType.typeArguments.first;
var expectedReturnType = _typeProvider.futureOrType2(targetFutureType);
if (callback is FunctionExpression) {
// TODO(migration): should be FunctionType, not nullable
var callbackType = callback.staticType as FunctionType;
_checkOnErrorFunctionType(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);
_checkOnErrorFunctionType(callback, callbackType, expectedReturnType);
} else {
// If [callback] is not even a Function, then ErrorVerifier will have
// reported this.
}
}
}
void _checkOnErrorFunctionType(Expression expression,
FunctionType expressionType, DartType expectedFunctionReturnType) {
void report() {
_errorReporter.reportErrorForNode(
HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE_CATCH_ERROR_ON_ERROR,
expression,
[expressionType, expectedFunctionReturnType],
);
}
var parameters = expressionType.parameters;
if (parameters.isEmpty) {
return report();
}
var firstParameter = parameters.first;
if (firstParameter.isNamed) {
return report();
} else {
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();
}
}
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],
);
}
}
}
/// 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);
}
}