| // Copyright (c) 2020, 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.md file. |
| |
| part of 'type_inferrer.dart'; |
| |
| /// Keeps track of information about the innermost function or closure being |
| /// inferred. |
| abstract class ClosureContext { |
| /// Returns `true` if this is an `async` or an `async*` function. |
| bool get isAsync; |
| |
| /// The typing expectation for the subexpression of a `return` statement |
| /// inside the function. |
| /// |
| /// For an `async` function, this is a "FutureOr" type (since it is |
| /// permissible for such a function to return either a direct value or a |
| /// future). |
| /// |
| /// For generator functions (which do not allow return statements) this is the |
| /// unknown type. |
| DartType get returnContext; |
| |
| /// The typing expectation for the subexpression of a `yield` statement inside |
| /// the function. |
| /// |
| /// For `sync*` and `async*` functions, the expected type is the element type |
| /// of the generated `Iterable` or `Stream`, respectively. |
| /// |
| /// For non-generator functions (which do not allow yield statements) this is |
| /// the unknown type. |
| DartType get yieldContext; |
| |
| DartType? get futureValueType; |
| |
| factory ClosureContext(TypeInferrerImpl inferrer, AsyncMarker asyncMarker, |
| DartType returnContext, bool needToInferReturnType) { |
| // ignore: unnecessary_null_comparison |
| assert(returnContext != null); |
| DartType declaredReturnType = |
| inferrer.computeGreatestClosure(returnContext); |
| bool isAsync = asyncMarker == AsyncMarker.Async || |
| asyncMarker == AsyncMarker.AsyncStar; |
| bool isGenerator = asyncMarker == AsyncMarker.SyncStar || |
| asyncMarker == AsyncMarker.AsyncStar; |
| if (isGenerator) { |
| if (isAsync) { |
| DartType yieldContext = inferrer.getTypeArgumentOf( |
| returnContext, inferrer.coreTypes.streamClass); |
| return new _AsyncStarClosureContext( |
| yieldContext, declaredReturnType, needToInferReturnType); |
| } else { |
| DartType yieldContext = inferrer.getTypeArgumentOf( |
| returnContext, inferrer.coreTypes.iterableClass); |
| return new _SyncStarClosureContext( |
| yieldContext, declaredReturnType, needToInferReturnType); |
| } |
| } else if (isAsync) { |
| DartType? futureValueType; |
| if (inferrer.isNonNullableByDefault) { |
| returnContext = inferrer.wrapFutureOrType( |
| inferrer.computeFutureValueTypeSchema(returnContext)); |
| if (!needToInferReturnType) { |
| futureValueType = |
| computeFutureValueType(inferrer.coreTypes, declaredReturnType); |
| } |
| } else { |
| returnContext = inferrer.wrapFutureOrType( |
| inferrer.typeSchemaEnvironment.flatten(returnContext)); |
| } |
| return new _AsyncClosureContext(returnContext, declaredReturnType, |
| needToInferReturnType, futureValueType); |
| } else { |
| return new _SyncClosureContext( |
| returnContext, declaredReturnType, needToInferReturnType); |
| } |
| } |
| |
| /// Handles an explicit return statement. |
| /// |
| /// If the return type is declared, the expression type is checked. If the |
| /// return type is inferred the expression type registered for inference |
| /// in [inferReturnType]. |
| void handleReturn(TypeInferrerImpl inferrer, ReturnStatement statement, |
| DartType type, bool isArrow); |
| |
| /// Handles an explicit yield statement. |
| /// |
| /// If the return type is declared, the expression type is checked. If the |
| /// return type is inferred the expression type registered for inference |
| /// in [inferReturnType]. |
| void handleYield(TypeInferrerImpl inferrer, YieldStatement node, |
| ExpressionInferenceResult expressionResult); |
| |
| /// Handles an implicit return statement. |
| /// |
| /// If the return type is declared, the expression type is checked. If the |
| /// return type is inferred the expression type registered for inference |
| /// in [inferReturnType]. |
| StatementInferenceResult handleImplicitReturn(TypeInferrerImpl inferrer, |
| Statement body, StatementInferenceResult inferenceResult, int fileOffset); |
| |
| /// Infers the return type for the function. |
| /// |
| /// If the function is a non-generator function this is based on the explicit |
| /// and implicit return statements registered in [handleReturn] and |
| /// [handleImplicitReturn]. |
| /// |
| /// If the function is a generator function this is based on the explicit |
| /// yield statements registered in [handleYield]. |
| DartType inferReturnType(TypeInferrerImpl inferrer, |
| {required bool hasImplicitReturn}); |
| } |
| |
| class _SyncClosureContext implements ClosureContext { |
| @override |
| bool get isAsync => false; |
| |
| /// The typing expectation for the subexpression of a `return` statement |
| /// inside the function. |
| final DartType _returnContext; |
| |
| @override |
| DartType get returnContext => _returnContext; |
| |
| @override |
| DartType get yieldContext => const UnknownType(); |
| |
| final DartType _declaredReturnType; |
| |
| final bool _needToInferReturnType; |
| |
| DartType? _inferredReturnType; |
| |
| /// Whether the function is an arrow function. |
| bool? _isArrow; |
| |
| /// A list of return statements in functions whose return type is being |
| /// inferred. |
| /// |
| /// The returns are checked for validity after the return type is inferred. |
| List<ReturnStatement>? _returnStatements; |
| |
| /// A list of return expression types in functions whose return type is |
| /// being inferred. |
| List<DartType>? _returnExpressionTypes; |
| |
| @override |
| DartType? get futureValueType => null; |
| |
| _SyncClosureContext(this._returnContext, this._declaredReturnType, |
| this._needToInferReturnType) { |
| if (_needToInferReturnType) { |
| _returnStatements = []; |
| _returnExpressionTypes = []; |
| } |
| } |
| |
| void _checkValidReturn(TypeInferrerImpl inferrer, DartType returnType, |
| ReturnStatement statement, DartType expressionType) { |
| assert(!inferrer.isTopLevel); |
| if (inferrer.isNonNullableByDefault) { |
| if (statement.expression == null) { |
| // It is a compile-time error if s is `return;`, unless T is void, |
| // dynamic, or Null. |
| if (returnType is VoidType || |
| returnType is DynamicType || |
| returnType is NullType) { |
| // Valid return; |
| } else { |
| statement.expression = inferrer.helper!.wrapInProblem( |
| new NullLiteral()..fileOffset = statement.fileOffset, |
| messageReturnWithoutExpressionSync, |
| statement.fileOffset, |
| noLength) |
| ..parent = statement; |
| } |
| } else { |
| if (_isArrow! && returnType is VoidType) { |
| // For `=> e` it is a compile-time error if T is not void, and it |
| // would have been a compile-time error to declare the function with |
| // the body `{ return e; }` rather than `=> e`. |
| return; |
| } |
| |
| if (returnType is VoidType && |
| !(expressionType is VoidType || |
| expressionType is DynamicType || |
| expressionType is NullType)) { |
| // It is a compile-time error if s is `return e;`, T is void, and S is |
| // neither void, dynamic, nor Null. |
| statement.expression = inferrer.helper!.wrapInProblem( |
| statement.expression!, |
| messageReturnFromVoidFunction, |
| statement.expression!.fileOffset, |
| noLength) |
| ..parent = statement; |
| } else if (!(returnType is VoidType || returnType is DynamicType) && |
| expressionType is VoidType) { |
| // It is a compile-time error if s is `return e;`, T is neither void |
| // nor dynamic, and S is void. |
| statement.expression = inferrer.helper!.wrapInProblem( |
| statement.expression!, |
| templateInvalidReturn.withArguments(expressionType, |
| _declaredReturnType, inferrer.isNonNullableByDefault), |
| statement.expression!.fileOffset, |
| noLength) |
| ..parent = statement; |
| } else if (expressionType is! VoidType) { |
| // It is a compile-time error if s is `return e;`, S is not void, and |
| // S is not assignable to T. |
| Expression expression = inferrer.ensureAssignable( |
| _returnContext, expressionType, statement.expression!, |
| fileOffset: statement.expression!.fileOffset, |
| isVoidAllowed: true, |
| errorTemplate: templateInvalidReturn, |
| nullabilityErrorTemplate: templateInvalidReturnNullability, |
| nullabilityPartErrorTemplate: |
| templateInvalidReturnPartNullability, |
| nullabilityNullErrorTemplate: |
| templateInvalidReturnNullabilityNull, |
| nullabilityNullTypeErrorTemplate: |
| templateInvalidReturnNullabilityNullType); |
| statement.expression = expression..parent = statement; |
| } |
| } |
| } else { |
| // The rules for valid returns for functions with [returnType] `T` and |
| // a return expression with static [expressionType] `S`. |
| if (statement.expression == null) { |
| // `return;` is a valid return if T is void, dynamic, or Null. |
| if (returnType is VoidType || |
| returnType is DynamicType || |
| returnType is NullType) { |
| // Valid return; |
| } else { |
| statement.expression = inferrer.helper!.wrapInProblem( |
| new NullLiteral()..fileOffset = statement.fileOffset, |
| messageReturnWithoutExpression, |
| statement.fileOffset, |
| noLength) |
| ..parent = statement; |
| } |
| } else { |
| void ensureAssignability() { |
| Expression expression = inferrer.ensureAssignable( |
| _returnContext, expressionType, statement.expression!, |
| fileOffset: statement.fileOffset, isVoidAllowed: true); |
| statement.expression = expression..parent = statement; |
| } |
| |
| if (_isArrow! && returnType is VoidType) { |
| // Arrow functions are valid if: T is void or return exp; is a valid |
| // for a block-bodied function. |
| ensureAssignability(); |
| } else if (returnType is VoidType && |
| expressionType is! VoidType && |
| expressionType is! DynamicType && |
| expressionType is! NullType) { |
| // Invalid if T is void and S is not void, dynamic, or Null |
| statement.expression = inferrer.helper!.wrapInProblem( |
| statement.expression!, |
| messageReturnFromVoidFunction, |
| statement.expression!.fileOffset, |
| noLength) |
| ..parent = statement; |
| } else if (expressionType is VoidType && |
| returnType is! VoidType && |
| returnType is! DynamicType && |
| returnType is! NullType) { |
| // Invalid if S is void and T is not void, dynamic, or Null. |
| statement.expression = inferrer.helper!.wrapInProblem( |
| statement.expression!, |
| messageVoidExpression, |
| statement.expression!.fileOffset, |
| noLength) |
| ..parent = statement; |
| } else { |
| ensureAssignability(); |
| } |
| } |
| } |
| } |
| |
| /// Updates the inferred return type based on the presence of a return |
| /// statement returning the given [type]. |
| @override |
| void handleReturn(TypeInferrerImpl inferrer, ReturnStatement statement, |
| DartType type, bool isArrow) { |
| // The first return we see tells us if we have an arrow function. |
| if (this._isArrow == null) { |
| this._isArrow = isArrow; |
| } else { |
| assert(this._isArrow == isArrow); |
| } |
| |
| if (_needToInferReturnType) { |
| // Add the return to a list to be checked for validity after we've |
| // inferred the return type. |
| _returnStatements!.add(statement); |
| _returnExpressionTypes!.add(type); |
| } else if (!inferrer.isTopLevel) { |
| _checkValidReturn(inferrer, _declaredReturnType, statement, type); |
| } |
| } |
| |
| @override |
| void handleYield(TypeInferrerImpl inferrer, YieldStatement node, |
| ExpressionInferenceResult expressionResult) { |
| node.expression = expressionResult.expression..parent = node; |
| } |
| |
| @override |
| DartType inferReturnType(TypeInferrerImpl inferrer, |
| {required bool hasImplicitReturn}) { |
| assert(_needToInferReturnType); |
| // ignore: unnecessary_null_comparison |
| assert(hasImplicitReturn != null); |
| DartType? actualReturnedType; |
| DartType inferredReturnType; |
| if (inferrer.isNonNullableByDefault) { |
| if (hasImplicitReturn) { |
| // No explicit returns we have an implicit `return null`. |
| actualReturnedType = const NullType(); |
| } else { |
| // No explicit return and the function doesn't complete normally; that |
| // is, it throws. |
| actualReturnedType = |
| NeverType.fromNullability(inferrer.library.nonNullable); |
| } |
| // Use the types seen from the explicit return statements. |
| for (int i = 0; i < _returnStatements!.length; i++) { |
| ReturnStatement statement = _returnStatements![i]; |
| DartType type = _returnExpressionTypes![i]; |
| // The return expression has to be assignable to the return type |
| // expectation from the downwards inference context. |
| if (statement.expression != null) { |
| if (!inferrer.isAssignable(_returnContext, type)) { |
| type = inferrer.computeGreatestClosure(_returnContext); |
| } |
| } |
| if (actualReturnedType == null) { |
| actualReturnedType = type; |
| } else { |
| actualReturnedType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound( |
| actualReturnedType, type, inferrer.library.library); |
| } |
| } |
| |
| // Let T be the actual returned type of a function literal as computed |
| // above. Let R be the greatest closure of the typing context K as |
| // computed above. |
| DartType returnContext = |
| inferrer.computeGreatestClosure2(_declaredReturnType); |
| if (returnContext is VoidType) { |
| // With null safety: if R is void, or the function literal is marked |
| // async and R is FutureOr<void>, let S be void. |
| inferredReturnType = const VoidType(); |
| } else if (inferrer.typeSchemaEnvironment.isSubtypeOf(actualReturnedType!, |
| returnContext, SubtypeCheckMode.withNullabilities)) { |
| // Otherwise, if T <: R then let S be T. |
| inferredReturnType = actualReturnedType; |
| } else { |
| // Otherwise, let S be R. |
| inferredReturnType = returnContext; |
| } |
| } else { |
| if (_returnStatements!.isNotEmpty) { |
| // Use the types seen from the explicit return statements. |
| for (int i = 0; i < _returnStatements!.length; i++) { |
| ReturnStatement statement = _returnStatements![i]; |
| DartType type = _returnExpressionTypes![i]; |
| // The return expression has to be assignable to the return type |
| // expectation from the downwards inference context. |
| if (statement.expression != null) { |
| if (!inferrer.isAssignable(_returnContext, type)) { |
| type = inferrer.computeGreatestClosure(_returnContext); |
| } |
| } |
| if (actualReturnedType == null) { |
| actualReturnedType = type; |
| } else { |
| actualReturnedType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound( |
| actualReturnedType, type, inferrer.library.library); |
| } |
| } |
| } else if (hasImplicitReturn) { |
| // No explicit returns we have an implicit `return null`. |
| actualReturnedType = const NullType(); |
| } else { |
| // No explicit return and the function doesn't complete normally; that |
| // is, it throws. |
| actualReturnedType = const NullType(); |
| } |
| |
| if (!inferrer.typeSchemaEnvironment.isSubtypeOf(actualReturnedType!, |
| _returnContext, SubtypeCheckMode.withNullabilities)) { |
| // If the inferred return type isn't a subtype of the context, we use |
| // the context. |
| inferredReturnType = |
| inferrer.computeGreatestClosure2(_declaredReturnType); |
| } else { |
| inferredReturnType = actualReturnedType; |
| } |
| } |
| |
| for (int i = 0; i < _returnStatements!.length; ++i) { |
| if (!inferrer.isTopLevel) { |
| _checkValidReturn(inferrer, inferredReturnType, _returnStatements![i], |
| _returnExpressionTypes![i]); |
| } |
| } |
| |
| return _inferredReturnType = |
| demoteTypeInLibrary(inferredReturnType, inferrer.library.library); |
| } |
| |
| @override |
| StatementInferenceResult handleImplicitReturn( |
| TypeInferrerImpl inferrer, |
| Statement body, |
| StatementInferenceResult inferenceResult, |
| int fileOffset) { |
| DartType returnType; |
| if (_needToInferReturnType) { |
| assert(_inferredReturnType != null, |
| "Return type has not yet been inferred."); |
| returnType = _inferredReturnType!; |
| } else { |
| returnType = _declaredReturnType; |
| } |
| if (!inferrer.isTopLevel && |
| inferrer.library.isNonNullableByDefault && |
| !containsInvalidType(returnType) && |
| returnType.isPotentiallyNonNullable && |
| inferrer.flowAnalysis.isReachable) { |
| Statement resultStatement = |
| inferenceResult.hasChanged ? inferenceResult.statement : body; |
| // Create a synthetic return statement with the error. |
| Statement returnStatement = new ReturnStatement(inferrer.helper! |
| .wrapInProblem( |
| new NullLiteral()..fileOffset = fileOffset, |
| templateImplicitReturnNull.withArguments( |
| returnType, inferrer.library.isNonNullableByDefault), |
| fileOffset, |
| noLength)) |
| ..fileOffset = fileOffset; |
| if (resultStatement is Block) { |
| resultStatement.statements.add(returnStatement); |
| } else { |
| resultStatement = |
| new Block(<Statement>[resultStatement, returnStatement]) |
| ..fileOffset = fileOffset; |
| } |
| return new StatementInferenceResult.single(resultStatement); |
| } |
| return inferenceResult; |
| } |
| } |
| |
| /// Keeps track of information about the innermost function or closure being |
| /// inferred. |
| class _AsyncClosureContext implements ClosureContext { |
| @override |
| bool get isAsync => true; |
| |
| /// The typing expectation for the subexpression of a `return` statement |
| /// inside the function. |
| /// |
| /// This will be a "FutureOr" type (since it is permissible for such a |
| /// function to return either a direct value or a future). |
| final DartType _returnContext; |
| |
| @override |
| DartType get returnContext => _returnContext; |
| |
| @override |
| DartType get yieldContext => const UnknownType(); |
| |
| final DartType _declaredReturnType; |
| |
| final bool _needToInferReturnType; |
| |
| DartType? _inferredReturnType; |
| |
| /// Whether the function is an arrow function. |
| bool? _isArrow; |
| |
| /// A list of return statements in functions whose return type is being |
| /// inferred. |
| /// |
| /// The returns are checked for validity after the return type is inferred. |
| List<ReturnStatement>? _returnStatements; |
| |
| /// A list of return expression types in functions whose return type is |
| /// being inferred. |
| List<DartType>? _returnExpressionTypes; |
| |
| @override |
| DartType? futureValueType; |
| |
| _AsyncClosureContext(this._returnContext, this._declaredReturnType, |
| this._needToInferReturnType, this.futureValueType) { |
| if (_needToInferReturnType) { |
| _returnStatements = []; |
| _returnExpressionTypes = []; |
| } |
| } |
| |
| void _checkValidReturn(TypeInferrerImpl inferrer, DartType returnType, |
| ReturnStatement statement, DartType expressionType) { |
| assert(!inferrer.isTopLevel); |
| if (inferrer.isNonNullableByDefault) { |
| assert( |
| futureValueType != null, "Future value type has not been computed."); |
| |
| if (statement.expression == null) { |
| // It is a compile-time error if s is `return;`, unless T_v is void, |
| // dynamic, or Null. |
| if (futureValueType is VoidType || |
| futureValueType is DynamicType || |
| futureValueType is NullType) { |
| // Valid return; |
| } else { |
| statement.expression = inferrer.helper!.wrapInProblem( |
| new NullLiteral()..fileOffset = statement.fileOffset, |
| messageReturnWithoutExpressionAsync, |
| statement.fileOffset, |
| noLength) |
| ..parent = statement; |
| } |
| } else { |
| if (_isArrow! && |
| inferrer.typeSchemaEnvironment.flatten(returnType) is VoidType) { |
| // For `async => e` it is a compile-time error if flatten(T) is not |
| // void, and it would have been a compile-time error to declare the |
| // function with the body `async { return e; }` rather than |
| // `async => e`. |
| return; |
| } |
| |
| DartType flattenedExpressionType = |
| inferrer.typeSchemaEnvironment.flatten(expressionType); |
| if (futureValueType is VoidType && |
| !(flattenedExpressionType is VoidType || |
| flattenedExpressionType is DynamicType || |
| flattenedExpressionType is NullType)) { |
| // It is a compile-time error if s is `return e;`, T_v is void, and |
| // flatten(S) is neither void, dynamic, Null. |
| statement.expression = inferrer.helper!.wrapInProblem( |
| new NullLiteral()..fileOffset = statement.fileOffset, |
| templateInvalidReturnAsync.withArguments( |
| expressionType, returnType, inferrer.isNonNullableByDefault), |
| statement.expression!.fileOffset, |
| noLength) |
| ..parent = statement; |
| } else if (!(futureValueType is VoidType || |
| futureValueType is DynamicType) && |
| flattenedExpressionType is VoidType) { |
| // It is a compile-time error if s is `return e;`, T_v is neither void |
| // nor dynamic, and flatten(S) is void. |
| statement.expression = inferrer.helper!.wrapInProblem( |
| new NullLiteral()..fileOffset = statement.fileOffset, |
| templateInvalidReturnAsync.withArguments( |
| expressionType, returnType, inferrer.isNonNullableByDefault), |
| statement.expression!.fileOffset, |
| noLength) |
| ..parent = statement; |
| } else if (flattenedExpressionType is! VoidType && |
| !inferrer.typeSchemaEnvironment |
| .performNullabilityAwareSubtypeCheck( |
| flattenedExpressionType, futureValueType!) |
| .isSubtypeWhenUsingNullabilities()) { |
| // It is a compile-time error if s is `return e;`, flatten(S) is not |
| // void, S is not assignable to T_v, and flatten(S) is not a subtype |
| // of T_v. |
| statement.expression = inferrer.ensureAssignable( |
| futureValueType!, expressionType, statement.expression!, |
| fileOffset: statement.expression!.fileOffset, |
| runtimeCheckedType: |
| inferrer.computeGreatestClosure2(_returnContext), |
| declaredContextType: returnType, |
| isVoidAllowed: false, |
| errorTemplate: templateInvalidReturnAsync, |
| nullabilityErrorTemplate: templateInvalidReturnAsyncNullability, |
| nullabilityPartErrorTemplate: |
| templateInvalidReturnAsyncPartNullability, |
| nullabilityNullErrorTemplate: |
| templateInvalidReturnAsyncNullabilityNull, |
| nullabilityNullTypeErrorTemplate: |
| templateInvalidReturnAsyncNullabilityNullType) |
| ..parent = statement; |
| } |
| } |
| } else { |
| // The rules for valid returns for async functions with [returnType] `T` |
| // and a return expression with static [expressionType] `S`. |
| DartType flattenedReturnType = |
| inferrer.typeSchemaEnvironment.flatten(returnType); |
| if (statement.expression == null) { |
| // `return;` is a valid return if flatten(T) is void, dynamic, or Null. |
| if (flattenedReturnType is VoidType || |
| flattenedReturnType is DynamicType || |
| flattenedReturnType is NullType) { |
| // Valid return; |
| } else { |
| statement.expression = inferrer.helper!.wrapInProblem( |
| new NullLiteral()..fileOffset = statement.fileOffset, |
| messageReturnWithoutExpression, |
| statement.fileOffset, |
| noLength) |
| ..parent = statement; |
| } |
| } else { |
| DartType flattenedExpressionType = |
| inferrer.typeSchemaEnvironment.flatten(expressionType); |
| |
| void ensureAssignability() { |
| DartType wrappedType = inferrer.typeSchemaEnvironment |
| .futureType(flattenedExpressionType, Nullability.nonNullable); |
| Expression expression = inferrer.ensureAssignable( |
| computeAssignableType(inferrer, _returnContext, wrappedType), |
| wrappedType, |
| statement.expression!, |
| fileOffset: statement.fileOffset, |
| isVoidAllowed: true, |
| runtimeCheckedType: |
| inferrer.computeGreatestClosure(_returnContext)); |
| statement.expression = expression..parent = statement; |
| } |
| |
| if (_isArrow! && flattenedReturnType is VoidType) { |
| // Arrow functions are valid if: flatten(T) is void or return exp; is |
| // valid for a block-bodied function. |
| ensureAssignability(); |
| } else if (returnType is VoidType && |
| flattenedExpressionType is! VoidType && |
| flattenedExpressionType is! DynamicType && |
| flattenedExpressionType is! NullType) { |
| // Invalid if T is void and flatten(S) is not void, dynamic, or Null. |
| statement.expression = inferrer.helper!.wrapInProblem( |
| statement.expression!, |
| messageReturnFromVoidFunction, |
| statement.expression!.fileOffset, |
| noLength) |
| ..parent = statement; |
| } else if (flattenedExpressionType is VoidType && |
| flattenedReturnType is! VoidType && |
| flattenedReturnType is! DynamicType && |
| flattenedReturnType is! NullType) { |
| // Invalid if flatten(S) is void and flatten(T) is not void, dynamic, |
| // or Null. |
| statement.expression = inferrer.helper!.wrapInProblem( |
| statement.expression!, |
| messageVoidExpression, |
| statement.expression!.fileOffset, |
| noLength) |
| ..parent = statement; |
| } else { |
| // The caller will check that the return expression is assignable to |
| // the return type. |
| ensureAssignability(); |
| } |
| } |
| } |
| } |
| |
| /// Updates the inferred return type based on the presence of a return |
| /// statement returning the given [type]. |
| @override |
| void handleReturn(TypeInferrerImpl inferrer, ReturnStatement statement, |
| DartType type, bool isArrow) { |
| // The first return we see tells us if we have an arrow function. |
| if (this._isArrow == null) { |
| this._isArrow = isArrow; |
| } else { |
| assert(this._isArrow == isArrow); |
| } |
| |
| if (_needToInferReturnType) { |
| // Add the return to a list to be checked for validity after we've |
| // inferred the return type. |
| _returnStatements!.add(statement); |
| _returnExpressionTypes!.add(type); |
| } else if (!inferrer.isTopLevel) { |
| _checkValidReturn(inferrer, _declaredReturnType, statement, type); |
| } |
| } |
| |
| @override |
| void handleYield(TypeInferrerImpl inferrer, YieldStatement node, |
| ExpressionInferenceResult expressionResult) { |
| node.expression = expressionResult.expression..parent = node; |
| } |
| |
| DartType computeAssignableType(TypeInferrerImpl inferrer, |
| DartType contextType, DartType expressionType) { |
| contextType = inferrer.computeGreatestClosure(contextType); |
| |
| DartType initialContextType = contextType; |
| if (!inferrer.isAssignable(initialContextType, expressionType)) { |
| // If the body of the function is async, the expected return type has the |
| // shape FutureOr<T>. We check both branches for FutureOr here: both T |
| // and Future<T>. |
| DartType unfuturedExpectedType = |
| inferrer.typeSchemaEnvironment.flatten(contextType); |
| DartType futuredExpectedType = inferrer.wrapFutureType( |
| unfuturedExpectedType, inferrer.library.nonNullable); |
| if (inferrer.isAssignable(unfuturedExpectedType, expressionType)) { |
| contextType = unfuturedExpectedType; |
| } else if (inferrer.isAssignable(futuredExpectedType, expressionType)) { |
| contextType = futuredExpectedType; |
| } |
| } |
| return contextType; |
| } |
| |
| @override |
| DartType inferReturnType(TypeInferrerImpl inferrer, |
| {required bool hasImplicitReturn}) { |
| assert(_needToInferReturnType); |
| // ignore: unnecessary_null_comparison |
| assert(hasImplicitReturn != null); |
| DartType? inferredType; |
| |
| if (inferrer.isNonNullableByDefault) { |
| if (hasImplicitReturn) { |
| // No explicit returns we have an implicit `return null`. |
| inferredType = const NullType(); |
| } else { |
| // No explicit return and the function doesn't complete normally; that |
| // is, it throws. |
| inferredType = NeverType.fromNullability(inferrer.library.nonNullable); |
| } |
| // Use the types seen from the explicit return statements. |
| for (int i = 0; i < _returnStatements!.length; i++) { |
| DartType type = _returnExpressionTypes![i]; |
| |
| DartType unwrappedType = inferrer.typeSchemaEnvironment.flatten(type); |
| if (inferredType == null) { |
| inferredType = unwrappedType; |
| } else { |
| inferredType = inferrer.typeSchemaEnvironment.getStandardUpperBound( |
| inferredType, unwrappedType, inferrer.library.library); |
| } |
| } |
| |
| // Let `T` be the **actual returned type** of a function literal as |
| // computed above. |
| |
| // Let `R` be the greatest closure of the typing context `K` as computed |
| // above. If `R` is `void`, or the function literal is marked `async` and |
| // `R` is `FutureOr<void>`, let `S` be `void`. Otherwise, if `T <: R` then |
| // let `S` be `T`. Otherwise, let `S` be `R`. |
| DartType returnContext = inferrer.computeGreatestClosure2(_returnContext); |
| if (returnContext is VoidType || |
| returnContext is FutureOrType && |
| returnContext.typeArgument is VoidType) { |
| inferredType = const VoidType(); |
| } else if (!inferrer.typeSchemaEnvironment.isSubtypeOf( |
| inferredType!, returnContext, SubtypeCheckMode.withNullabilities)) { |
| // If the inferred return type isn't a subtype of the context, we use |
| // the context. |
| inferredType = returnContext; |
| } |
| inferredType = inferrer.wrapFutureType( |
| inferrer.typeSchemaEnvironment.flatten(inferredType), |
| inferrer.library.nonNullable); |
| } else { |
| if (_returnStatements!.isNotEmpty) { |
| // Use the types seen from the explicit return statements. |
| for (int i = 0; i < _returnStatements!.length; i++) { |
| ReturnStatement statement = _returnStatements![i]; |
| DartType type = _returnExpressionTypes![i]; |
| |
| // The return expression has to be assignable to the return type |
| // expectation from the downwards inference context. |
| if (statement.expression != null) { |
| if (!inferrer.isAssignable( |
| computeAssignableType(inferrer, _returnContext, type), type)) { |
| // Not assignable, use the expectation. |
| type = inferrer.computeGreatestClosure(_returnContext); |
| } |
| } |
| DartType unwrappedType = inferrer.typeSchemaEnvironment.flatten(type); |
| if (inferredType == null) { |
| inferredType = unwrappedType; |
| } else { |
| inferredType = inferrer.typeSchemaEnvironment.getStandardUpperBound( |
| inferredType, unwrappedType, inferrer.library.library); |
| } |
| } |
| } else if (hasImplicitReturn) { |
| // No explicit returns we have an implicit `return null`. |
| inferredType = const NullType(); |
| } else { |
| // No explicit return and the function doesn't complete normally; |
| // that is, it throws. |
| inferredType = const NullType(); |
| } |
| inferredType = |
| inferrer.wrapFutureType(inferredType!, inferrer.library.nonNullable); |
| |
| if (!inferrer.typeSchemaEnvironment.isSubtypeOf( |
| inferredType, _returnContext, SubtypeCheckMode.withNullabilities)) { |
| // If the inferred return type isn't a subtype of the context, we use |
| // the context. |
| inferredType = inferrer.computeGreatestClosure2(_declaredReturnType); |
| } |
| } |
| |
| if (inferrer.isNonNullableByDefault) { |
| futureValueType = |
| computeFutureValueType(inferrer.coreTypes, inferredType); |
| } |
| if (!inferrer.isTopLevel) { |
| for (int i = 0; i < _returnStatements!.length; ++i) { |
| _checkValidReturn(inferrer, inferredType, _returnStatements![i], |
| _returnExpressionTypes![i]); |
| } |
| } |
| |
| return _inferredReturnType = |
| demoteTypeInLibrary(inferredType, inferrer.library.library); |
| } |
| |
| @override |
| StatementInferenceResult handleImplicitReturn( |
| TypeInferrerImpl inferrer, |
| Statement body, |
| StatementInferenceResult inferenceResult, |
| int fileOffset) { |
| DartType returnType; |
| if (_needToInferReturnType) { |
| assert(_inferredReturnType != null, |
| "Return type has not yet been inferred."); |
| returnType = _inferredReturnType!; |
| } else { |
| returnType = _declaredReturnType; |
| } |
| returnType = inferrer.typeSchemaEnvironment.flatten(returnType); |
| if (!inferrer.isTopLevel && |
| inferrer.library.isNonNullableByDefault && |
| !containsInvalidType(returnType) && |
| returnType.isPotentiallyNonNullable && |
| inferrer.flowAnalysis.isReachable) { |
| Statement resultStatement = |
| inferenceResult.hasChanged ? inferenceResult.statement : body; |
| // Create a synthetic return statement with the error. |
| Statement returnStatement = new ReturnStatement(inferrer.helper! |
| .wrapInProblem( |
| new NullLiteral()..fileOffset = fileOffset, |
| templateImplicitReturnNull.withArguments( |
| returnType, inferrer.library.isNonNullableByDefault), |
| fileOffset, |
| noLength)) |
| ..fileOffset = fileOffset; |
| if (resultStatement is Block) { |
| resultStatement.statements.add(returnStatement); |
| } else { |
| resultStatement = |
| new Block(<Statement>[resultStatement, returnStatement]) |
| ..fileOffset = fileOffset; |
| } |
| return new StatementInferenceResult.single(resultStatement); |
| } |
| return inferenceResult; |
| } |
| } |
| |
| /// Keeps track of information about the innermost function or closure being |
| /// inferred. |
| class _SyncStarClosureContext implements ClosureContext { |
| @override |
| bool get isAsync => false; |
| |
| /// The typing expectation for the subexpression of a `return` or `yield` |
| /// statement inside the function. |
| /// |
| /// For non-generator async functions, this will be a "FutureOr" type (since |
| /// it is permissible for such a function to return either a direct value or |
| /// a future). |
| /// |
| /// For generator functions containing a `yield*` statement, the expected type |
| /// for the subexpression of the `yield*` statement is the result of wrapping |
| /// this typing expectation in `Stream` or `Iterator`, as appropriate. |
| final DartType _yieldElementContext; |
| |
| @override |
| DartType get returnContext => const UnknownType(); |
| |
| @override |
| DartType get yieldContext => _yieldElementContext; |
| |
| final DartType _declaredReturnType; |
| |
| final bool _needToInferReturnType; |
| |
| /// A list of return expression types in functions whose return type is |
| /// being inferred. |
| List<DartType>? _yieldElementTypes; |
| |
| @override |
| DartType? get futureValueType => null; |
| |
| _SyncStarClosureContext(this._yieldElementContext, this._declaredReturnType, |
| this._needToInferReturnType) { |
| if (_needToInferReturnType) { |
| _yieldElementTypes = []; |
| } |
| } |
| |
| /// Updates the inferred return type based on the presence of a return |
| /// statement returning the given [type]. |
| @override |
| void handleReturn(TypeInferrerImpl inferrer, ReturnStatement statement, |
| DartType type, bool isArrow) {} |
| |
| @override |
| void handleYield(TypeInferrerImpl inferrer, YieldStatement node, |
| ExpressionInferenceResult expressionResult) { |
| DartType expectedType = node.isYieldStar |
| ? inferrer.wrapType(_yieldElementContext, |
| inferrer.coreTypes.iterableClass, inferrer.library.nonNullable) |
| : _yieldElementContext; |
| Expression expression = inferrer.ensureAssignableResult( |
| expectedType, expressionResult, |
| fileOffset: node.fileOffset); |
| node.expression = expression..parent = node; |
| DartType type = expressionResult.inferredType; |
| if (!identical(expressionResult.expression, expression)) { |
| type = inferrer.computeGreatestClosure(expectedType); |
| } |
| if (_needToInferReturnType) { |
| DartType elementType = type; |
| if (node.isYieldStar) { |
| elementType = inferrer.getDerivedTypeArgumentOf( |
| type, inferrer.coreTypes.iterableClass) ?? |
| elementType; |
| } |
| _yieldElementTypes!.add(elementType); |
| } |
| } |
| |
| @override |
| DartType inferReturnType(TypeInferrerImpl inferrer, |
| {required bool hasImplicitReturn}) { |
| assert(_needToInferReturnType); |
| // ignore: unnecessary_null_comparison |
| assert(hasImplicitReturn != null); |
| DartType? inferredElementType; |
| if (_yieldElementTypes!.isNotEmpty) { |
| // Use the types seen from the explicit return statements. |
| for (int i = 0; i < _yieldElementTypes!.length; i++) { |
| DartType type = _yieldElementTypes![i]; |
| if (inferredElementType == null) { |
| inferredElementType = type; |
| } else { |
| inferredElementType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound( |
| inferredElementType, type, inferrer.library.library); |
| } |
| } |
| } else if (hasImplicitReturn) { |
| // No explicit returns we have an implicit `return null`. |
| inferredElementType = const NullType(); |
| } else { |
| // No explicit return and the function doesn't complete normally; that is, |
| // it throws. |
| if (inferrer.isNonNullableByDefault) { |
| inferredElementType = |
| NeverType.fromNullability(inferrer.library.nonNullable); |
| } else { |
| inferredElementType = const NullType(); |
| } |
| } |
| |
| DartType inferredType = inferrer.wrapType(inferredElementType!, |
| inferrer.coreTypes.iterableClass, inferrer.library.nonNullable); |
| |
| if (!inferrer.typeSchemaEnvironment.isSubtypeOf(inferredType, |
| _yieldElementContext, SubtypeCheckMode.withNullabilities)) { |
| // If the inferred return type isn't a subtype of the context, we use the |
| // context. |
| inferredType = inferrer.computeGreatestClosure2(_declaredReturnType); |
| } |
| |
| return demoteTypeInLibrary(inferredType, inferrer.library.library); |
| } |
| |
| @override |
| StatementInferenceResult handleImplicitReturn( |
| TypeInferrerImpl inferrer, |
| Statement body, |
| StatementInferenceResult inferenceResult, |
| int fileOffset) { |
| // There is no implicit return. |
| return inferenceResult; |
| } |
| } |
| |
| /// Keeps track of information about the innermost function or closure being |
| /// inferred. |
| class _AsyncStarClosureContext implements ClosureContext { |
| @override |
| bool get isAsync => true; |
| |
| /// The typing expectation for the subexpression of a `return` or `yield` |
| /// statement inside the function. |
| /// |
| /// For non-generator async functions, this will be a "FutureOr" type (since |
| /// it is permissible for such a function to return either a direct value or |
| /// a future). |
| /// |
| /// For generator functions containing a `yield*` statement, the expected type |
| /// for the subexpression of the `yield*` statement is the result of wrapping |
| /// this typing expectation in `Stream` or `Iterator`, as appropriate. |
| final DartType _yieldElementContext; |
| |
| @override |
| DartType get returnContext => const UnknownType(); |
| |
| @override |
| DartType get yieldContext => _yieldElementContext; |
| |
| final DartType _declaredReturnType; |
| |
| final bool _needToInferReturnType; |
| |
| /// A list of return expression types in functions whose return type is |
| /// being inferred. |
| List<DartType>? _yieldElementTypes; |
| |
| @override |
| DartType? get futureValueType => null; |
| |
| _AsyncStarClosureContext(this._yieldElementContext, this._declaredReturnType, |
| this._needToInferReturnType) { |
| if (_needToInferReturnType) { |
| _yieldElementTypes = []; |
| } |
| } |
| |
| /// Updates the inferred return type based on the presence of a return |
| /// statement returning the given [type]. |
| @override |
| void handleReturn(TypeInferrerImpl inferrer, ReturnStatement statement, |
| DartType type, bool isArrow) {} |
| |
| @override |
| void handleYield(TypeInferrerImpl inferrer, YieldStatement node, |
| ExpressionInferenceResult expressionResult) { |
| DartType expectedType = node.isYieldStar |
| ? inferrer.wrapType(_yieldElementContext, |
| inferrer.coreTypes.streamClass, inferrer.library.nonNullable) |
| : _yieldElementContext; |
| |
| Expression expression = inferrer.ensureAssignableResult( |
| expectedType, expressionResult, |
| fileOffset: node.fileOffset); |
| node.expression = expression..parent = node; |
| DartType type = expressionResult.inferredType; |
| if (!identical(expressionResult.expression, expression)) { |
| type = inferrer.computeGreatestClosure(expectedType); |
| } |
| if (_needToInferReturnType) { |
| DartType elementType = type; |
| if (node.isYieldStar) { |
| elementType = inferrer.getDerivedTypeArgumentOf( |
| type, inferrer.coreTypes.streamClass) ?? |
| type; |
| } |
| _yieldElementTypes!.add(elementType); |
| } |
| } |
| |
| @override |
| DartType inferReturnType(TypeInferrerImpl inferrer, |
| {required bool hasImplicitReturn}) { |
| assert(_needToInferReturnType); |
| // ignore: unnecessary_null_comparison |
| assert(hasImplicitReturn != null); |
| DartType? inferredElementType; |
| if (_yieldElementTypes!.isNotEmpty) { |
| // Use the types seen from the explicit return statements. |
| for (DartType elementType in _yieldElementTypes!) { |
| if (inferredElementType == null) { |
| inferredElementType = elementType; |
| } else { |
| inferredElementType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound( |
| inferredElementType, elementType, inferrer.library.library); |
| } |
| } |
| } else if (hasImplicitReturn) { |
| // No explicit returns we have an implicit `return null`. |
| inferredElementType = const NullType(); |
| } else { |
| // No explicit return and the function doesn't complete normally; that is, |
| // it throws. |
| if (inferrer.isNonNullableByDefault) { |
| inferredElementType = |
| NeverType.fromNullability(inferrer.library.nonNullable); |
| } else { |
| inferredElementType = const NullType(); |
| } |
| } |
| |
| DartType inferredType = inferrer.wrapType(inferredElementType!, |
| inferrer.coreTypes.streamClass, inferrer.library.nonNullable); |
| |
| if (!inferrer.typeSchemaEnvironment.isSubtypeOf(inferredType, |
| _yieldElementContext, SubtypeCheckMode.withNullabilities)) { |
| // If the inferred return type isn't a subtype of the context, we use the |
| // context. |
| inferredType = inferrer.computeGreatestClosure2(_declaredReturnType); |
| } |
| |
| return demoteTypeInLibrary(inferredType, inferrer.library.library); |
| } |
| |
| @override |
| StatementInferenceResult handleImplicitReturn( |
| TypeInferrerImpl inferrer, |
| Statement body, |
| StatementInferenceResult inferenceResult, |
| int fileOffset) { |
| // There is no implicit return. |
| return inferenceResult; |
| } |
| } |