blob: 2ae92c7ed08a0d8207113a6c59c819e0d47ee1fe [file] [log] [blame]
// 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 file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.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.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/error/analyzer_error_code.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/error_verifier.dart';
class ReturnTypeVerifier {
final TypeProviderImpl _typeProvider;
final TypeSystemImpl _typeSystem;
final ErrorReporter _errorReporter;
late EnclosingExecutableContext enclosingExecutable;
ReturnTypeVerifier({
required TypeProviderImpl typeProvider,
required TypeSystemImpl typeSystem,
required ErrorReporter errorReporter,
}) : _typeProvider = typeProvider,
_typeSystem = typeSystem,
_errorReporter = errorReporter;
DartType get _flattenedReturnType {
var returnType = enclosingExecutable.returnType;
if (enclosingExecutable.isSynchronous) {
return returnType;
} else {
return _typeSystem.flatten(returnType);
}
}
void verifyExpressionFunctionBody(ExpressionFunctionBody node) {
// This enables concise declarations of void functions.
if (_flattenedReturnType.isVoid) {
return;
}
return _checkReturnExpression(node.expression);
}
void verifyReturnStatement(ReturnStatement statement) {
var expression = statement.expression;
if (enclosingExecutable.isGenerativeConstructor) {
if (expression != null) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_IN_GENERATIVE_CONSTRUCTOR,
expression,
);
}
return;
}
if (enclosingExecutable.isGenerator) {
return;
}
if (expression == null) {
_checkReturnWithoutValue(statement);
return;
}
_checkReturnExpression(expression);
}
void verifyReturnType(TypeAnnotation? returnType) {
// If no declared type, then the type is `dynamic`, which is valid.
if (returnType == null) {
return;
}
void checkElement(
ClassElement expectedElement,
AnalyzerErrorCode errorCode,
) {
void reportError() {
enclosingExecutable.hasLegalReturnType = false;
_errorReporter.reportErrorForNode(errorCode, returnType);
}
// It is a compile-time error if the declared return type of
// a function marked `sync*` or `async*` is `void`.
if (enclosingExecutable.isGenerator) {
if (enclosingExecutable.returnType.isVoid) {
return reportError();
}
}
// It is a compile-time error if the declared return type of
// a function marked `...` is not a supertype of `...`.
if (!_isLegalReturnType(expectedElement)) {
return reportError();
}
}
if (enclosingExecutable.isAsynchronous) {
if (enclosingExecutable.isGenerator) {
checkElement(
_typeProvider.streamElement,
CompileTimeErrorCode.ILLEGAL_ASYNC_GENERATOR_RETURN_TYPE,
);
} else {
checkElement(
_typeProvider.futureElement,
CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE,
);
}
} else if (enclosingExecutable.isGenerator) {
checkElement(
_typeProvider.iterableElement,
CompileTimeErrorCode.ILLEGAL_SYNC_GENERATOR_RETURN_TYPE,
);
}
}
/// Check that a type mismatch between the type of the [expression] and
/// the expected return type of the enclosing executable.
void _checkReturnExpression(Expression expression) {
if (!enclosingExecutable.hasLegalReturnType) {
// ILLEGAL_ASYNC_RETURN_TYPE has already been reported, meaning the
// _declared_ return type is illegal; don't confuse by also reporting
// that the type being returned here does not match that illegal return
// type.
return;
}
if (enclosingExecutable.isGenerator) {
// [CompileTimeErrorCode.RETURN_IN_GENERATOR] has already been reported;
// do not report a duplicate error.
return;
}
if (_typeSystem.isNonNullableByDefault) {
_checkReturnExpression_nullSafety(expression);
} else {
_checkReturnExpression_legacy(expression);
}
}
void _checkReturnExpression_legacy(Expression expression) {
// `T` is the declared return type.
// `S` is the static type of the expression.
var T = enclosingExecutable.returnType;
var S = expression.typeOrThrow;
void reportTypeError() {
if (enclosingExecutable.catchErrorOnErrorReturnType != null) {
_errorReporter.reportErrorForNode(
HintCode.RETURN_OF_INVALID_TYPE_FROM_CATCH_ERROR,
expression,
[S, T],
);
} else if (enclosingExecutable.isClosure) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_CLOSURE,
expression,
[S, T],
);
} else if (enclosingExecutable.isConstructor) {
// [EnclosingExecutableContext.displayName] will only return `null` if
// there is no enclosing element, in which case the `if` test above
// would have failed. So it's safe to assume that
// `enclosingExecutable.displayName` is non-`null`.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_CONSTRUCTOR,
expression,
[S, T, enclosingExecutable.displayName!],
);
} else if (enclosingExecutable.isFunction) {
// [EnclosingExecutableContext.displayName] will only return `null` if
// there is no enclosing element, in which case the `if` test above
// would have failed. So it's safe to assume that
// `enclosingExecutable.displayName` is non-`null`.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION,
expression,
[S, T, enclosingExecutable.displayName!],
);
} else if (enclosingExecutable.isMethod) {
// [EnclosingExecutableContext.displayName] will only return `null` if
// there is no enclosing element, in which case the `if` test above
// would have failed. So it's safe to assume that
// `enclosingExecutable.displayName` is non-`null`.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD,
expression,
[S, T, enclosingExecutable.displayName!],
);
}
}
if (enclosingExecutable.isSynchronous) {
// It is a compile-time error if `T` is `void`,
// and `S` is neither `void`, `dynamic`, nor `Null`.
if (T.isVoid) {
if (!_isVoidDynamicOrNull(S)) {
reportTypeError();
return;
}
}
// It is a compile-time error if `S` is `void`,
// and `T` is neither `void`, `dynamic`, nor `Null`.
if (S.isVoid) {
if (!_isVoidDynamicOrNull(T)) {
reportTypeError();
return;
}
}
// It is a compile-time error if `S` is not `void`,
// and `S` is not assignable to `T`.
if (!S.isVoid) {
if (!_typeSystem.isAssignableTo(S, T)) {
reportTypeError();
return;
}
}
// OK
return;
}
if (enclosingExecutable.isAsynchronous) {
var flatten_T = _typeSystem.flatten(T);
var flatten_S = _typeSystem.flatten(S);
// It is a compile-time error if `T` is `void`,
// and `flatten(S)` is neither `void`, `dynamic`, nor `Null`.
//
// Note, the specification was not implemented correctly, and
// implementing it now would be a breaking change. So, the code below
// intentionally does not implement the specification.
// https://github.com/dart-lang/sdk/issues/41803#issuecomment-635852474
if (T.isVoid) {
if (!_isVoidDynamicOrNull(flatten_S)) {
reportTypeError();
return;
}
}
// It is a compile-time error if `flatten(S)` is `void`,
// and `flatten(T)` is neither `void`, `dynamic`, nor `Null`.
if (flatten_S.isVoid) {
if (!_isVoidDynamicOrNull(flatten_T)) {
reportTypeError();
return;
}
}
// It is a compile-time error if `flatten(S)` is not `void`,
// and `Future<flatten(S)>` is not assignable to `T`.
if (!flatten_S.isVoid) {
var future_flatten_S = _typeProvider.futureType(flatten_S);
if (!_typeSystem.isAssignableTo(future_flatten_S, T)) {
reportTypeError();
return;
}
// OK
return;
}
}
}
void _checkReturnExpression_nullSafety(Expression expression) {
// `T` is the declared return type.
// `S` is the static type of the expression.
var T = enclosingExecutable.returnType;
var S = expression.typeOrThrow;
void reportTypeError() {
if (enclosingExecutable.catchErrorOnErrorReturnType != null) {
_errorReporter.reportErrorForNode(
HintCode.RETURN_OF_INVALID_TYPE_FROM_CATCH_ERROR,
expression,
[S, T],
);
} else if (enclosingExecutable.isClosure) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_CLOSURE,
expression,
[S, T],
);
} else if (enclosingExecutable.isConstructor) {
// [EnclosingExecutableContext.displayName] will only return `null` if
// there is no enclosing element, in which case the `if` test above
// would have failed. So it's safe to assume that
// `enclosingExecutable.displayName` is non-`null`.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_CONSTRUCTOR,
expression,
[S, T, enclosingExecutable.displayName!],
);
} else if (enclosingExecutable.isFunction) {
// [EnclosingExecutableContext.displayName] will only return `null` if
// there is no enclosing element, in which case the `if` test above
// would have failed. So it's safe to assume that
// `enclosingExecutable.displayName` is non-`null`.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION,
expression,
[S, T, enclosingExecutable.displayName!],
);
} else if (enclosingExecutable.isMethod) {
// [EnclosingExecutableContext.displayName] will only return `null` if
// there is no enclosing element, in which case the `if` test above
// would have failed. So it's safe to assume that
// `enclosingExecutable.displayName` is non-`null`.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD,
expression,
[S, T, enclosingExecutable.displayName!],
);
}
}
if (enclosingExecutable.isSynchronous) {
// It is a compile-time error if `T` is `void`,
// and `S` is neither `void`, `dynamic`, nor `Null`.
if (T.isVoid) {
if (!_isVoidDynamicOrNull(S)) {
reportTypeError();
return;
}
}
// It is a compile-time error if `S` is `void`,
// and `T` is neither `void` nor `dynamic`.
if (S.isVoid) {
if (!_isVoidDynamic(T)) {
reportTypeError();
return;
}
}
// It is a compile-time error if `S` is not `void`,
// and `S` is not assignable to `T`.
if (!S.isVoid) {
if (!_typeSystem.isAssignableTo(S, T)) {
reportTypeError();
return;
}
}
// OK
return;
}
if (enclosingExecutable.isAsynchronous) {
var T_v = _typeSystem.futureValueType(T);
var flatten_S = _typeSystem.flatten(S);
// It is a compile-time error if `flatten(T)` is `void`,
// and `flatten(S)` is neither `void`, `dynamic`, nor `Null`.
if (T_v.isVoid) {
if (!_isVoidDynamicOrNull(flatten_S)) {
reportTypeError();
return;
}
}
// It is a compile-time error if `flatten(S)` is `void`,
// and `flatten(T)` is neither `void`, `dynamic`.
if (flatten_S.isVoid) {
if (!_isVoidDynamic(T_v)) {
reportTypeError();
return;
}
}
// It is a compile-time error if `flatten(S)` is not `void`,
// and `Future<flatten(S)>` is not assignable to `T`.
if (!flatten_S.isVoid) {
if (!_typeSystem.isAssignableTo(S, T_v) &&
!_typeSystem.isSubtypeOf(flatten_S, T_v)) {
reportTypeError();
return;
}
// OK
return;
}
}
}
void _checkReturnWithoutValue(ReturnStatement statement) {
if (_typeSystem.isNonNullableByDefault) {
var T = enclosingExecutable.returnType;
if (enclosingExecutable.isSynchronous) {
if (_isVoidDynamicOrNull(T)) {
return;
}
} else {
var T_v = _typeSystem.futureValueType(T);
if (_isVoidDynamicOrNull(T_v)) {
return;
}
}
} else {
var returnType = _flattenedReturnType;
if (_isVoidDynamicOrNull(returnType)) {
return;
}
}
_errorReporter.reportErrorForToken(
CompileTimeErrorCode.RETURN_WITHOUT_VALUE,
statement.returnKeyword,
);
}
bool _isLegalReturnType(ClassElement expectedElement) {
DartType returnType = enclosingExecutable.returnType;
//
// When checking an async/sync*/async* method, we know the exact type
// that will be returned (e.g. Future, Iterable, or Stream).
//
// For example an `async` function body will return a `Future<T>` for
// some `T` (possibly `dynamic`).
//
// We allow the declared return type to be a supertype of that
// (e.g. `dynamic`, `Object`), or Future<S> for some S.
// (We assume the T <: S relation is checked elsewhere.)
//
// We do not allow user-defined subtypes of Future, because an `async`
// method will never return those.
//
// To check for this, we ensure that `Future<bottom> <: returnType`.
//
// Similar logic applies for sync* and async*.
//
var lowerBound = expectedElement.instantiate(
typeArguments: [NeverTypeImpl.instance],
nullabilitySuffix: NullabilitySuffix.star,
);
return _typeSystem.isSubtypeOf(lowerBound, returnType);
}
static bool _isVoidDynamic(DartType type) {
return type.isVoid || type.isDynamic;
}
static bool _isVoidDynamicOrNull(DartType type) {
return type.isVoid || type.isDynamic || type.isDartCoreNull;
}
}