| // 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. | 
 |  | 
 | /// @docImport 'dart:async'; | 
 | library; | 
 |  | 
 | 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/ast.dart'; | 
 | import 'package:analyzer/src/dart/element/type.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' argument to [Stream.listen], | 
 | /// * 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 DiagnosticReporter _diagnosticReporter; | 
 |  | 
 |   final TypeProviderImpl _typeProvider; | 
 |  | 
 |   final TypeSystemImpl _typeSystem; | 
 |  | 
 |   final ReturnTypeVerifier _returnTypeVerifier; | 
 |  | 
 |   final bool _strictCasts; | 
 |  | 
 |   ErrorHandlerVerifier( | 
 |     this._diagnosticReporter, | 
 |     this._typeProvider, | 
 |     this._typeSystem, { | 
 |     required bool strictCasts, | 
 |   }) : _strictCasts = strictCasts, | 
 |        _returnTypeVerifier = ReturnTypeVerifier( | 
 |          typeProvider: _typeProvider, | 
 |          typeSystem: _typeSystem, | 
 |          diagnosticReporter: _diagnosticReporter, | 
 |          strictCasts: strictCasts, | 
 |        ); | 
 |  | 
 |   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) { | 
 |         // TODO(srawlins): The comment below is wrong, given | 
 |         // `named-arguments-anywhere`. | 
 |         // 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 FunctionTypeImpl) { | 
 |         // 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 FunctionTypeImpl) { | 
 |         _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 FunctionTypeImpl) { | 
 |         _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 FunctionTypeImpl) { | 
 |         _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, | 
 |     FunctionTypeImpl expressionType, | 
 |     DartType expectedFunctionReturnType, { | 
 |     bool checkFirstParameterType = true, | 
 |   }) { | 
 |     void report() { | 
 |       _diagnosticReporter.atNode( | 
 |         expression, | 
 |         WarningCode.argumentTypeNotAssignableToErrorHandler, | 
 |         arguments: [expressionType, expectedFunctionReturnType], | 
 |       ); | 
 |     } | 
 |  | 
 |     var parameters = expressionType.formalParameters; | 
 |     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 InterfaceTypeImpl; | 
 |     var targetFutureType = targetType.typeArguments.first; | 
 |     var expectedReturnType = _typeProvider.futureOrType(targetFutureType); | 
 |     if (callback is FunctionExpressionImpl) { | 
 |       // TODO(migration): should be FunctionType, not nullable | 
 |       var callbackType = callback.staticType as FunctionTypeImpl; | 
 |       _checkErrorHandlerFunctionType( | 
 |         callback, | 
 |         callbackType, | 
 |         expectedReturnType, | 
 |       ); | 
 |       var catchErrorOnErrorExecutable = EnclosingExecutableContext( | 
 |         callback.declaredFragment!.element, | 
 |         isAsynchronous: true, | 
 |         isGenerator: false, | 
 |         catchErrorOnErrorReturnType: expectedReturnType, | 
 |       ); | 
 |       var returnStatementVerifier = _ReturnStatementVerifier( | 
 |         _returnTypeVerifier, | 
 |       ); | 
 |       _returnTypeVerifier.enclosingExecutable = catchErrorOnErrorExecutable; | 
 |       callback.body.accept(returnStatementVerifier); | 
 |     } else { | 
 |       var callbackType = callback.staticType; | 
 |       if (callbackType is FunctionTypeImpl) { | 
 |         _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( | 
 |     TypeImpl expectedType, | 
 |     TypeImpl functionReturnType, | 
 |     Expression callback, | 
 |   ) { | 
 |     if (!_typeSystem.isAssignableTo( | 
 |       functionReturnType, | 
 |       expectedType, | 
 |       strictCasts: _strictCasts, | 
 |     )) { | 
 |       _diagnosticReporter.atNode( | 
 |         callback, | 
 |         WarningCode.returnTypeInvalidForCatchError, | 
 |         arguments: [functionReturnType, expectedType], | 
 |       ); | 
 |     } | 
 |   } | 
 |  | 
 |   /// Returns whether [type] represents the type named [typeName], declared in | 
 |   /// the 'dart:async' library. | 
 |   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); | 
 |   } | 
 | } |