blob: 0906db377761e77cb5ce5f55079cffc859872406 [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:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.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/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/invocation_inference_helper.dart';
import 'package:analyzer/src/dart/resolver/resolution_result.dart';
import 'package:analyzer/src/dart/resolver/type_property_resolver.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
/// Helper for resolving [BinaryExpression]s.
class BinaryExpressionResolver {
final ResolverVisitor _resolver;
final TypePropertyResolver _typePropertyResolver;
final InvocationInferenceHelper _inferenceHelper;
BinaryExpressionResolver({
required ResolverVisitor resolver,
}) : _resolver = resolver,
_typePropertyResolver = resolver.typePropertyResolver,
_inferenceHelper = resolver.inferenceHelper;
ErrorReporter get _errorReporter => _resolver.errorReporter;
bool get _isNonNullableByDefault => _typeSystem.isNonNullableByDefault;
TypeProvider get _typeProvider => _resolver.typeProvider;
TypeSystemImpl get _typeSystem => _resolver.typeSystem;
void resolve(BinaryExpressionImpl node, {required DartType? contextType}) {
var operator = node.operator.type;
if (operator == TokenType.AMPERSAND_AMPERSAND) {
_resolveLogicalAnd(node, contextType: contextType);
return;
}
if (operator == TokenType.BANG_EQ || operator == TokenType.EQ_EQ) {
_resolveEqual(node,
notEqual: operator == TokenType.BANG_EQ, contextType: contextType);
return;
}
if (operator == TokenType.BAR_BAR) {
_resolveLogicalOr(node, contextType: contextType);
return;
}
if (operator == TokenType.QUESTION_QUESTION) {
_resolveIfNull(node, contextType: contextType);
return;
}
if (operator.isUserDefinableOperator && operator.isBinaryOperator) {
_resolveUserDefinable(node, contextType: contextType);
return;
}
// Report an error if not already reported by the parser.
if (operator != TokenType.BANG_EQ_EQ && operator != TokenType.EQ_EQ_EQ) {
_errorReporter.reportErrorForToken(
CompileTimeErrorCode.NOT_BINARY_OPERATOR,
node.operator,
[operator.lexeme]);
}
_resolveUnsupportedOperator(node, contextType: contextType);
}
/// Set the static type of [node] to be the least upper bound of the static
/// types [staticType1] and [staticType2].
///
/// TODO(scheglov) this is duplicate
void _analyzeLeastUpperBoundTypes(
ExpressionImpl node, DartType staticType1, DartType staticType2,
{required DartType? contextType}) {
var staticType = _typeSystem.getLeastUpperBound(staticType1, staticType2);
staticType = _resolver.toLegacyTypeIfOptOut(staticType);
_inferenceHelper.recordStaticType(node, staticType,
contextType: contextType);
}
void _checkNonBoolOperand(Expression operand, String operator,
{required Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
_resolver.boolExpressionVerifier.checkForNonBoolExpression(
operand,
errorCode: CompileTimeErrorCode.NON_BOOL_OPERAND,
arguments: [operator],
whyNotPromoted: whyNotPromoted,
);
}
void _resolveEqual(BinaryExpressionImpl node,
{required bool notEqual, required DartType? contextType}) {
var left = node.leftOperand;
left.accept(_resolver);
left = node.leftOperand;
var flow = _resolver.flowAnalysis.flow;
EqualityInfo<PromotableElement, DartType>? leftInfo;
var leftExtensionOverride = left is ExtensionOverride;
if (!leftExtensionOverride) {
leftInfo = flow?.equalityOperand_end(left, left.typeOrThrow);
}
var right = node.rightOperand;
right.accept(_resolver);
right = node.rightOperand;
var whyNotPromoted = _resolver.flowAnalysis.flow?.whyNotPromoted(right);
if (!leftExtensionOverride) {
flow?.equalityOperation_end(
node, leftInfo, flow.equalityOperand_end(right, right.typeOrThrow),
notEqual: notEqual);
}
_resolveUserDefinableElement(
node,
TokenType.EQ_EQ.lexeme,
promoteLeftTypeToNonNull: true,
);
_resolveUserDefinableType(node, contextType: contextType);
_resolver.checkForArgumentTypeNotAssignableForArgument(node.rightOperand,
promoteParameterToNullable: true, whyNotPromoted: whyNotPromoted);
}
void _resolveIfNull(BinaryExpressionImpl node,
{required DartType? contextType}) {
var left = node.leftOperand;
var right = node.rightOperand;
var flow = _resolver.flowAnalysis.flow;
var leftContextType = contextType;
if (leftContextType != null && _isNonNullableByDefault) {
leftContextType = _typeSystem.makeNullable(leftContextType);
}
_resolver.analyzeExpression(left, leftContextType);
left = node.leftOperand;
var leftType = left.typeOrThrow;
var rightContextType = contextType;
if (rightContextType == null || rightContextType.isDynamic) {
rightContextType = leftType;
}
flow?.ifNullExpression_rightBegin(left, leftType);
_resolver.analyzeExpression(right, rightContextType);
right = node.rightOperand;
flow?.ifNullExpression_end();
var rightType = right.typeOrThrow;
if (_isNonNullableByDefault) {
var promotedLeftType = _typeSystem.promoteToNonNull(leftType);
_analyzeLeastUpperBoundTypes(node, promotedLeftType, rightType,
contextType: contextType);
} else {
_analyzeLeastUpperBoundTypes(node, leftType, rightType,
contextType: contextType);
}
_resolver.checkForArgumentTypeNotAssignableForArgument(right);
}
void _resolveLogicalAnd(BinaryExpressionImpl node,
{required DartType? contextType}) {
var left = node.leftOperand;
var right = node.rightOperand;
var flow = _resolver.flowAnalysis.flow;
flow?.logicalBinaryOp_begin();
_resolver.analyzeExpression(left, _typeProvider.boolType);
left = node.leftOperand;
var leftWhyNotPromoted = _resolver.flowAnalysis.flow?.whyNotPromoted(left);
flow?.logicalBinaryOp_rightBegin(left, node, isAnd: true);
_resolver.checkUnreachableNode(right);
_resolver.analyzeExpression(right, _typeProvider.boolType);
right = node.rightOperand;
var rightWhyNotPromoted =
_resolver.flowAnalysis.flow?.whyNotPromoted(right);
_resolver.nullSafetyDeadCodeVerifier.flowEnd(right);
flow?.logicalBinaryOp_end(node, right, isAnd: true);
_checkNonBoolOperand(left, '&&', whyNotPromoted: leftWhyNotPromoted);
_checkNonBoolOperand(right, '&&', whyNotPromoted: rightWhyNotPromoted);
_inferenceHelper.recordStaticType(node, _typeProvider.boolType,
contextType: contextType);
}
void _resolveLogicalOr(BinaryExpressionImpl node,
{required DartType? contextType}) {
var left = node.leftOperand;
var right = node.rightOperand;
var flow = _resolver.flowAnalysis.flow;
flow?.logicalBinaryOp_begin();
_resolver.analyzeExpression(left, _typeProvider.boolType);
left = node.leftOperand;
var leftWhyNotPromoted = _resolver.flowAnalysis.flow?.whyNotPromoted(left);
flow?.logicalBinaryOp_rightBegin(left, node, isAnd: false);
_resolver.checkUnreachableNode(right);
_resolver.analyzeExpression(right, _typeProvider.boolType);
right = node.rightOperand;
var rightWhyNotPromoted =
_resolver.flowAnalysis.flow?.whyNotPromoted(right);
_resolver.nullSafetyDeadCodeVerifier.flowEnd(right);
flow?.logicalBinaryOp_end(node, right, isAnd: false);
_checkNonBoolOperand(left, '||', whyNotPromoted: leftWhyNotPromoted);
_checkNonBoolOperand(right, '||', whyNotPromoted: rightWhyNotPromoted);
_inferenceHelper.recordStaticType(node, _typeProvider.boolType,
contextType: contextType);
}
void _resolveUnsupportedOperator(BinaryExpressionImpl node,
{required DartType? contextType}) {
node.leftOperand.accept(_resolver);
node.rightOperand.accept(_resolver);
_inferenceHelper.recordStaticType(node, DynamicTypeImpl.instance,
contextType: contextType);
}
void _resolveUserDefinable(BinaryExpressionImpl node,
{required DartType? contextType}) {
var left = node.leftOperand;
var right = node.rightOperand;
left.accept(_resolver);
left = node.leftOperand; // In case it was rewritten
var operator = node.operator;
_resolveUserDefinableElement(node, operator.lexeme);
var invokeType = node.staticInvokeType;
DartType? rightContextType;
if (invokeType != null && invokeType.parameters.isNotEmpty) {
// If this is a user-defined operator, set the right operand context
// using the operator method's parameter type.
var rightParam = invokeType.parameters[0];
rightContextType = _typeSystem.refineNumericInvocationContext(
left.staticType, node.staticElement, contextType, rightParam.type);
}
_resolver.analyzeExpression(right, rightContextType);
right = node.rightOperand;
var whyNotPromoted = _resolver.flowAnalysis.flow?.whyNotPromoted(right);
_resolveUserDefinableType(node, contextType: contextType);
_resolver.checkForArgumentTypeNotAssignableForArgument(right,
whyNotPromoted: whyNotPromoted);
}
void _resolveUserDefinableElement(
BinaryExpressionImpl node,
String methodName, {
bool promoteLeftTypeToNonNull = false,
}) {
Expression leftOperand = node.leftOperand;
if (leftOperand is ExtensionOverride) {
var extension =
leftOperand.extensionName.staticElement as ExtensionElement;
var member = extension.getMethod(methodName);
if (member == null) {
// Extension overrides can only be used with named extensions so it is
// safe to assume `extension.name` is non-`null`.
_errorReporter.reportErrorForToken(
CompileTimeErrorCode.UNDEFINED_EXTENSION_OPERATOR,
node.operator,
[methodName, extension.name!],
);
}
node.staticElement = member;
node.staticInvokeType = member?.type;
return;
}
var leftType = leftOperand.typeOrThrow;
leftType = _typeSystem.resolveToBound(leftType);
if (identical(leftType, NeverTypeImpl.instance)) {
_resolver.errorReporter.reportErrorForNode(
HintCode.RECEIVER_OF_TYPE_NEVER,
leftOperand,
);
return;
}
if (promoteLeftTypeToNonNull) {
leftType = _typeSystem.promoteToNonNull(leftType);
}
ResolutionResult result = _typePropertyResolver.resolve(
receiver: leftOperand,
receiverType: leftType,
name: methodName,
propertyErrorEntity: node.operator,
nameErrorEntity: node,
);
node.staticElement = result.getter as MethodElement?;
node.staticInvokeType = result.getter?.type;
if (result.needsGetterError) {
if (leftOperand is SuperExpression) {
_errorReporter.reportErrorForToken(
CompileTimeErrorCode.UNDEFINED_SUPER_OPERATOR,
node.operator,
[methodName, leftType],
);
} else {
_errorReporter.reportErrorForToken(
CompileTimeErrorCode.UNDEFINED_OPERATOR,
node.operator,
[methodName, leftType],
);
}
}
}
void _resolveUserDefinableType(BinaryExpressionImpl node,
{required DartType? contextType}) {
var leftOperand = node.leftOperand;
DartType leftType;
if (leftOperand is ExtensionOverrideImpl) {
leftType = leftOperand.extendedType!;
} else {
leftType = leftOperand.typeOrThrow;
leftType = _typeSystem.resolveToBound(leftType);
}
if (identical(leftType, NeverTypeImpl.instance)) {
_inferenceHelper.recordStaticType(node, NeverTypeImpl.instance,
contextType: contextType);
return;
}
DartType staticType =
node.staticInvokeType?.returnType ?? DynamicTypeImpl.instance;
if (leftOperand is! ExtensionOverride) {
staticType = _typeSystem.refineBinaryExpressionType(
leftType,
node.operator.type,
node.rightOperand.typeOrThrow,
staticType,
node.staticElement,
);
}
_inferenceHelper.recordStaticType(node, staticType,
contextType: contextType);
}
}