blob: 69f798f192dfc084f3707b8be1354cc29c6beca3 [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/element/type.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/error_verifier.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:meta/meta.dart';
class ReturnTypeVerifier {
final TypeProviderImpl _typeProvider;
final TypeSystemImpl _typeSystem;
final ErrorReporter _errorReporter;
EnclosingExecutableContext enclosingExecutable;
ReturnTypeVerifier({
@required TypeProviderImpl typeProvider,
@required TypeSystemImpl typeSystem,
@required ErrorReporter errorReporter,
}) : _typeProvider = typeProvider,
_typeSystem = typeSystem,
_errorReporter = errorReporter;
/// Check that a type mis-match between the type of the [expression] and
/// the [expectedReturnType] by the enclosing method or function.
///
/// This method is called both by [_checkForAllReturnStatementErrorCodes]
/// and [visitExpressionFunctionBody].
void verifyReturnExpression(Expression expression, DartType expectedType,
{bool isArrowFunction = false}) {
if (enclosingExecutable == null) {
return;
}
if (enclosingExecutable.isGenerator) {
// "return expression;" is disallowed in generators, but this is checked
// elsewhere. Bare "return" is always allowed in generators regardless
// of the return type. So no need to do any further checking.
return;
}
if (expression == null) {
return; // Empty returns are handled elsewhere
}
DartType expressionType = getStaticType(expression);
var toType = expectedType;
var fromType = expressionType;
if (enclosingExecutable.isAsynchronous) {
toType = _typeSystem.flatten(toType);
fromType = _typeSystem.flatten(fromType);
if (!_isLegalReturnType(_typeProvider.futureElement)) {
// 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;
}
}
void reportTypeError() {
String displayName = enclosingExecutable.element.displayName;
if (displayName.isEmpty) {
_errorReporter.reportErrorForNode(
StaticTypeWarningCode.RETURN_OF_INVALID_TYPE_FROM_CLOSURE,
expression,
[fromType, toType]);
} else if (enclosingExecutable.isMethod) {
_errorReporter.reportErrorForNode(
StaticTypeWarningCode.RETURN_OF_INVALID_TYPE_FROM_METHOD,
expression,
[fromType, toType, displayName]);
} else {
_errorReporter.reportErrorForNode(
StaticTypeWarningCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION,
expression,
[fromType, toType, displayName]);
}
}
// Anything can be returned to `void` in an arrow bodied function
// or to `Future<void>` in an async arrow bodied function.
if (isArrowFunction && toType.isVoid) {
return;
}
if (toType.isVoid) {
if (fromType.isVoid ||
fromType.isDynamic ||
fromType.isDartCoreNull ||
fromType.isBottom) {
return;
}
} else if (fromType.isVoid) {
if (toType.isDynamic || toType.isDartCoreNull || toType.isBottom) {
return;
}
}
if (!expectedType.isVoid && !fromType.isVoid) {
var checkWithType = !enclosingExecutable.isAsynchronous
? fromType
: _typeProvider.futureType2(fromType);
if (_typeSystem.isAssignableTo2(checkWithType, expectedType)) {
return;
}
}
reportTypeError();
}
void verifyReturnStatement(ReturnStatement statement) {
FunctionType functionType = enclosingExecutable.element.type;
DartType expectedReturnType = functionType == null
? DynamicTypeImpl.instance
: functionType.returnType;
Expression returnExpression = statement.expression;
// RETURN_IN_GENERATIVE_CONSTRUCTOR
bool isGenerativeConstructor(ExecutableElement element) =>
element is ConstructorElement && !element.isFactory;
if (isGenerativeConstructor(enclosingExecutable.element)) {
if (returnExpression == null) {
return;
}
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_IN_GENERATIVE_CONSTRUCTOR,
returnExpression);
return;
}
// RETURN_WITHOUT_VALUE
if (returnExpression == null) {
_checkForAllEmptyReturnStatementErrorCodes(statement, expectedReturnType);
return;
} else if (enclosingExecutable.isGenerator) {
// RETURN_IN_GENERATOR
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_IN_GENERATOR,
statement,
[enclosingExecutable.isAsynchronous ? "async*" : "sync*"]);
return;
}
verifyReturnExpression(returnExpression, expectedReturnType);
}
void verifyReturnType(TypeAnnotation returnType) {
// If no declared type, then the type is `dynamic`, which is valid.
if (returnType == null) {
return;
}
if (enclosingExecutable.isAsynchronous) {
if (enclosingExecutable.isGenerator) {
_checkForIllegalReturnTypeCode(
returnType,
_typeProvider.streamElement,
StaticTypeWarningCode.ILLEGAL_ASYNC_GENERATOR_RETURN_TYPE,
);
} else {
_checkForIllegalReturnTypeCode(
returnType,
_typeProvider.futureElement,
StaticTypeWarningCode.ILLEGAL_ASYNC_RETURN_TYPE,
);
}
} else if (enclosingExecutable.isGenerator) {
_checkForIllegalReturnTypeCode(
returnType,
_typeProvider.iterableElement,
StaticTypeWarningCode.ILLEGAL_SYNC_GENERATOR_RETURN_TYPE,
);
}
}
/// Check that return statements without expressions are not in a generative
/// constructor and the return type is not assignable to `null`; that is, we
/// don't have `return;` if the enclosing method has a non-void containing
/// return type.
void _checkForAllEmptyReturnStatementErrorCodes(
ReturnStatement statement, DartType expectedReturnType) {
if (enclosingExecutable.isGenerator) {
return;
}
var returnType = enclosingExecutable.isAsynchronous
? _typeSystem.flatten(expectedReturnType)
: expectedReturnType;
if (returnType.isDynamic ||
returnType.isDartCoreNull ||
returnType.isVoid) {
return;
}
// If we reach here, this is an invalid return
_errorReporter.reportErrorForToken(
StaticWarningCode.RETURN_WITHOUT_VALUE, statement.returnKeyword);
return;
}
/// If the current function is async, async*, or sync*, verify that its
/// declared return type is assignable to Future, Stream, or Iterable,
/// respectively. This is called by [_checkForIllegalReturnType] to check if
/// a value with the type of the declared [returnTypeName] is assignable to
/// [expectedElement] and if not report [errorCode].
void _checkForIllegalReturnTypeCode(TypeAnnotation returnTypeName,
ClassElement expectedElement, StaticTypeWarningCode errorCode) {
if (!_isLegalReturnType(expectedElement)) {
_errorReporter.reportErrorForNode(errorCode, returnTypeName);
}
}
/// Returns whether a value with the type of the the enclosing function's
/// declared return type is assignable to [expectedElement].
bool _isLegalReturnType(ClassElement expectedElement) {
DartType returnType = enclosingExecutable.element.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.isSubtypeOf2(lowerBound, returnType);
}
/// Return the static type of the given [expression] that is to be used for
/// type analysis.
///
/// TODO(scheglov) this is duplicate
static DartType getStaticType(Expression expression) {
DartType type = expression.staticType;
if (type == null) {
// TODO(brianwilkerson) This should never happen.
return DynamicTypeImpl.instance;
}
return type;
}
}