blob: 9928a169fc31ed2be159b625c57e0b90f714113f [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/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/type_system.dart';
import 'package:meta/meta.dart';
/// Helper for resolving [YieldStatement]s.
class YieldStatementResolver {
final ResolverVisitor _resolver;
YieldStatementResolver({
@required ResolverVisitor resolver,
}) : _resolver = resolver;
ExecutableElement get _enclosingFunction => _resolver.enclosingFunction;
ErrorReporter get _errorReporter => _resolver.errorReporter;
TypeProvider get _typeProvider => _resolver.typeProvider;
TypeSystemImpl get _typeSystem => _resolver.typeSystem;
void resolve(YieldStatement node) {
if (_enclosingFunction?.isGenerator ?? false) {
_resolve_generator(node);
} else {
_resolve_notGenerator(node);
}
}
/// Check for situations where the result of a method or function is used, when
/// it returns 'void'. Or, in rare cases, when other types of expressions are
/// void, such as identifiers.
///
/// See [StaticWarningCode.USE_OF_VOID_RESULT].
///
/// TODO(scheglov) This is duplicate
/// TODO(scheglov) Also in [BoolExpressionVerifier]
bool _checkForUseOfVoidResult(Expression expression) {
if (!identical(expression.staticType, VoidTypeImpl.instance)) {
return false;
}
if (expression is MethodInvocation) {
_errorReporter.reportErrorForNode(
StaticWarningCode.USE_OF_VOID_RESULT,
expression.methodName,
);
} else {
_errorReporter.reportErrorForNode(
StaticWarningCode.USE_OF_VOID_RESULT,
expression,
);
}
return true;
}
/// Check for a type mis-match between the yielded type and the declared
/// return type of a generator function.
///
/// This method should only be called in generator functions.
void _checkForYieldOfInvalidType(YieldStatement node, bool isYieldEach) {
var declaredReturnType = _enclosingFunction.returnType;
var expression = node.expression;
var expressionType = expression.staticType;
DartType impliedReturnType;
if (isYieldEach) {
impliedReturnType = expressionType;
} else if (_enclosingFunction.isSynchronous) {
impliedReturnType = _typeProvider.iterableType2(expressionType);
} else {
impliedReturnType = _typeProvider.streamType2(expressionType);
}
if (declaredReturnType != null) {
if (!_typeSystem.isAssignableTo2(impliedReturnType, declaredReturnType)) {
_errorReporter.reportErrorForNode(
StaticTypeWarningCode.YIELD_OF_INVALID_TYPE,
expression,
[impliedReturnType, declaredReturnType],
);
return;
}
}
if (isYieldEach) {
// Since the declared return type might have been "dynamic", we need to
// also check that the implied return type is assignable to generic
// Iterable/Stream.
DartType requiredReturnType;
if (_enclosingFunction.isSynchronous) {
requiredReturnType = _typeProvider.iterableDynamicType;
} else {
requiredReturnType = _typeProvider.streamDynamicType;
}
if (!_typeSystem.isAssignableTo2(impliedReturnType, requiredReturnType)) {
_errorReporter.reportErrorForNode(
StaticTypeWarningCode.YIELD_OF_INVALID_TYPE,
expression,
[impliedReturnType, requiredReturnType],
);
}
}
}
void _computeElementType(YieldStatement node) {
var elementType = _resolver.inferenceContext.returnContext;
if (elementType != null) {
var contextType = elementType;
if (node.star != null) {
contextType = _enclosingFunction.isSynchronous
? _typeProvider.iterableType2(elementType)
: _typeProvider.streamType2(elementType);
}
InferenceContext.setType(node.expression, contextType);
}
}
void _resolve_generator(YieldStatement node) {
_computeElementType(node);
node.expression.accept(_resolver);
if (node.star != null) {
_resolver.nullableDereferenceVerifier.expression(node.expression);
}
DartType type = node.expression?.staticType;
// If this just a yield, then we just pass on the element type
if (node.star != null) {
// If this is a yield*, then we unwrap the element return type
// If it's synchronous, we expect Iterable<T>, otherwise Stream<T>
if (type is InterfaceType) {
ClassElement wrapperElement = _enclosingFunction.isSynchronous
? _typeProvider.iterableElement
: _typeProvider.streamElement;
var asInstanceType =
(type as InterfaceTypeImpl).asInstanceOf(wrapperElement);
if (asInstanceType != null) {
type = asInstanceType.typeArguments[0];
}
}
}
_resolver.inferenceContext.addReturnOrYieldType(type);
_checkForYieldOfInvalidType(node, node.star != null);
_checkForUseOfVoidResult(node.expression);
}
void _resolve_notGenerator(YieldStatement node) {
node.expression.accept(_resolver);
_errorReporter.reportErrorForNode(
node.star != null
? CompileTimeErrorCode.YIELD_EACH_IN_NON_GENERATOR
: CompileTimeErrorCode.YIELD_IN_NON_GENERATOR,
node,
);
_checkForUseOfVoidResult(node.expression);
}
}