blob: eb2431e1bfd8beb2006aaeabe5d3289cf06c7b30 [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:_fe_analyzer_shared/src/types/shared_type.dart';
import 'package:analyzer/dart/analysis/features.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/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/element.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_schema.dart';
import 'package:analyzer/src/dart/element/type_system.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 [AssignmentExpression]s.
class AssignmentExpressionResolver {
final ResolverVisitor _resolver;
final TypePropertyResolver _typePropertyResolver;
final AssignmentExpressionShared _assignmentShared;
AssignmentExpressionResolver({required ResolverVisitor resolver})
: _resolver = resolver,
_typePropertyResolver = resolver.typePropertyResolver,
_assignmentShared = AssignmentExpressionShared(resolver: resolver);
DiagnosticReporter get _diagnosticReporter => _resolver.diagnosticReporter;
TypeProviderImpl get _typeProvider => _resolver.typeProvider;
TypeSystemImpl get _typeSystem => _resolver.typeSystem;
void resolve(AssignmentExpressionImpl node, {required TypeImpl contextType}) {
var operator = node.operator.type;
var hasRead = operator != TokenType.EQ;
var isIfNull = operator == TokenType.QUESTION_QUESTION_EQ;
var leftResolution = _resolver.resolveForWrite(
node: node.leftHandSide,
hasRead: hasRead,
);
var left = node.leftHandSide;
var right = node.rightHandSide;
var readElement = leftResolution.readElement2;
var writeElement = leftResolution.writeElement2;
var writeElement2 = leftResolution.writeElement2;
if (hasRead) {
_resolver.setReadElement(
left,
readElement,
atDynamicTarget: leftResolution.atDynamicTarget,
);
{
var recordField = leftResolution.recordField;
if (recordField != null) {
node.readType = recordField.type;
}
}
_resolveOperator(node);
}
_resolver.setWriteElement(
left,
writeElement,
atDynamicTarget: leftResolution.atDynamicTarget,
);
// TODO(scheglov): Use VariableElement and do in resolveForWrite() ?
_assignmentShared.checkFinalAlreadyAssigned(left);
TypeImpl rhsContext;
{
var leftType = node.writeType;
if (writeElement is VariableElement) {
leftType = _resolver.localVariableTypeProvider.getType(
left as SimpleIdentifierImpl,
isRead: false,
);
}
rhsContext = _computeRhsContext(node, leftType!, operator, right);
}
var flow = _resolver.flowAnalysis.flow;
if (flow != null && isIfNull) {
flow.ifNullExpression_rightBegin(left, SharedTypeView(node.readType!));
}
_resolver.analyzeExpression(right, SharedTypeSchemaView(rhsContext));
right = _resolver.popRewrite()!;
var whyNotPromoted = flow?.whyNotPromoted(right);
_resolveTypes(
node,
whyNotPromoted: whyNotPromoted,
contextType: contextType,
);
if (flow != null) {
if (writeElement2 is PromotableElementImpl) {
flow.write(
node,
writeElement2,
SharedTypeView(node.typeOrThrow),
hasRead ? null : right,
);
}
if (isIfNull) {
flow.ifNullExpression_end();
}
}
_resolver.nullShortingTermination(node);
}
void _checkForInvalidAssignment(
TypeImpl writeType,
Expression right,
TypeImpl rightType, {
required Map<SharedTypeView, NonPromotionReason> Function()? whyNotPromoted,
}) {
if (writeType is! VoidType && _checkForUseOfVoidResult(right)) {
return;
}
var strictCasts = _resolver.analysisOptions.strictCasts;
if (_typeSystem.isAssignableTo(
rightType,
writeType,
strictCasts: strictCasts,
)) {
return;
}
if (writeType is RecordTypeImpl &&
writeType.positionalFields.length == 1 &&
rightType is! RecordType &&
right is ParenthesizedExpressionImpl) {
var field = writeType.positionalFields.first;
if (_typeSystem.isAssignableTo(
field.type,
rightType,
strictCasts: strictCasts,
)) {
_diagnosticReporter.atNode(
right,
CompileTimeErrorCode.RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA,
);
return;
}
}
_diagnosticReporter.atNode(
right,
CompileTimeErrorCode.INVALID_ASSIGNMENT,
arguments: [rightType, writeType],
contextMessages: _resolver.computeWhyNotPromotedMessages(
right,
whyNotPromoted?.call(),
),
);
}
/// 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 [CompileTimeErrorCode.USE_OF_VOID_RESULT].
// TODO(scheglov): this is duplicate
bool _checkForUseOfVoidResult(Expression expression) {
if (!identical(expression.staticType, VoidTypeImpl.instance)) {
return false;
}
if (expression is MethodInvocation) {
SimpleIdentifier methodName = expression.methodName;
_diagnosticReporter.atNode(
methodName,
CompileTimeErrorCode.USE_OF_VOID_RESULT,
);
} else {
_diagnosticReporter.atNode(
expression,
CompileTimeErrorCode.USE_OF_VOID_RESULT,
);
}
return true;
}
TypeImpl _computeRhsContext(
AssignmentExpressionImpl node,
TypeImpl leftType,
TokenType operator,
Expression right,
) {
switch (operator) {
case TokenType.EQ:
case TokenType.QUESTION_QUESTION_EQ:
return leftType;
case TokenType.AMPERSAND_AMPERSAND_EQ:
case TokenType.BAR_BAR_EQ:
return _typeProvider.boolType;
default:
var method = node.element;
if (method != null) {
var parameters = method.formalParameters;
if (parameters.isNotEmpty) {
return _typeSystem.refineNumericInvocationContext(
leftType,
method,
leftType,
parameters[0].type,
);
}
}
return UnknownInferredType.instance;
}
}
void _resolveOperator(AssignmentExpressionImpl node) {
var left = node.leftHandSide;
var operator = node.operator;
var operatorType = operator.type;
var leftType = node.readType!;
if (identical(leftType, NeverTypeImpl.instance)) {
return;
}
// Values of the type void cannot be used.
// Example: `y += 0`, is not allowed.
if (operatorType != TokenType.EQ) {
if (leftType is VoidType) {
_diagnosticReporter.atToken(
operator,
CompileTimeErrorCode.USE_OF_VOID_RESULT,
);
return;
}
}
if (operatorType == TokenType.AMPERSAND_AMPERSAND_EQ ||
operatorType == TokenType.BAR_BAR_EQ ||
operatorType == TokenType.EQ ||
operatorType == TokenType.QUESTION_QUESTION_EQ) {
return;
}
var binaryOperatorType = operatorType.binaryOperatorOfCompoundAssignment;
if (binaryOperatorType == null) {
return;
}
var methodName = binaryOperatorType.lexeme;
var result = _typePropertyResolver.resolve(
receiver: left,
receiverType: leftType,
name: methodName,
hasRead: operatorType != TokenType.EQ,
hasWrite: true,
propertyErrorEntity: operator,
nameErrorEntity: operator,
);
node.element = result.getter2 as InternalMethodElement?;
if (result.needsGetterError) {
_diagnosticReporter.atToken(
operator,
CompileTimeErrorCode.UNDEFINED_OPERATOR,
arguments: [methodName, leftType],
);
}
}
void _resolveTypes(
AssignmentExpressionImpl node, {
required Map<SharedTypeView, NonPromotionReason> Function()? whyNotPromoted,
required TypeImpl contextType,
}) {
TypeImpl assignedType;
var rightHandSide = node.rightHandSide;
var operator = node.operator.type;
if (operator == TokenType.EQ) {
assignedType = rightHandSide.typeOrThrow;
} else if (operator == TokenType.QUESTION_QUESTION_EQ) {
assignedType = rightHandSide.typeOrThrow;
} else if (operator == TokenType.AMPERSAND_AMPERSAND_EQ ||
operator == TokenType.BAR_BAR_EQ) {
assignedType = _typeProvider.boolType;
} else {
var leftType = node.readType!;
var operatorElement = node.element;
if (leftType is DynamicType) {
assignedType = DynamicTypeImpl.instance;
} else if (operatorElement != null) {
var rightType = rightHandSide.typeOrThrow;
assignedType = _typeSystem.refineBinaryExpressionType(
leftType,
operator,
rightType,
operatorElement.returnType,
operatorElement,
);
} else {
assignedType = InvalidTypeImpl.instance;
}
}
DartType nodeType;
if (operator == TokenType.QUESTION_QUESTION_EQ) {
// - An if-null assignment `E` of the form `lvalue ??= e` with context type
// `K` is analyzed as follows:
//
// - Let `T1` be the read type the lvalue.
var t1 = node.readType!;
// - Let `T2` be the type of `e` inferred with context type `T1`.
var t2 = assignedType;
// - Let `T` be `UP(NonNull(T1), T2)`.
var nonNullT1 = _typeSystem.promoteToNonNull(t1);
var t = _typeSystem.leastUpperBound(nonNullT1, t2);
// - Let `S` be the greatest closure of `K`.
var s =
_resolver.operations
.greatestClosureOfSchema(SharedTypeSchemaView(contextType))
.unwrapTypeView<TypeImpl>();
// If `inferenceUpdate3` is not enabled, then the type of `E` is `T`.
if (!_resolver.definingLibrary.featureSet.isEnabled(
Feature.inference_update_3,
)) {
nodeType = t;
} else
// - If `T <: S`, then the type of `E` is `T`.
if (_typeSystem.isSubtypeOf(t, s)) {
nodeType = t;
} else
// - Otherwise, if `NonNull(T1) <: S` and `T2 <: S`, then the type of
// `E` is `S`.
if (_typeSystem.isSubtypeOf(nonNullT1, s) &&
_typeSystem.isSubtypeOf(t2, s)) {
nodeType = s;
} else
// - Otherwise, the type of `E` is `T`.
{
nodeType = t;
}
} else {
nodeType = assignedType;
}
node.recordStaticType(nodeType, resolver: _resolver);
// TODO(scheglov): Remove from ErrorVerifier?
_checkForInvalidAssignment(
node.writeType!,
node.rightHandSide,
assignedType,
whyNotPromoted: operator == TokenType.EQ ? whyNotPromoted : null,
);
if (operator != TokenType.EQ &&
operator != TokenType.QUESTION_QUESTION_EQ) {
_resolver.checkForArgumentTypeNotAssignableForArgument(
node.rightHandSide,
whyNotPromoted: whyNotPromoted,
);
}
}
}
class AssignmentExpressionShared {
final ResolverVisitor _resolver;
AssignmentExpressionShared({required ResolverVisitor resolver})
: _resolver = resolver;
DiagnosticReporter get _errorReporter => _resolver.diagnosticReporter;
void checkFinalAlreadyAssigned(
Expression left, {
bool isForEachIdentifier = false,
}) {
var flowAnalysis = _resolver.flowAnalysis;
var flow = flowAnalysis.flow;
if (flow == null) return;
if (left is SimpleIdentifier) {
var element = left.element;
if (element is PromotableElementImpl) {
var assigned = flowAnalysis.isDefinitelyAssigned(left, element);
var unassigned = flowAnalysis.isDefinitelyUnassigned(left, element);
if (element.isFinal) {
if (element.isLate) {
if (isForEachIdentifier || assigned) {
_errorReporter.atNode(
left,
CompileTimeErrorCode.LATE_FINAL_LOCAL_ALREADY_ASSIGNED,
);
}
} else {
if (isForEachIdentifier || !unassigned) {
_errorReporter.atNode(
left,
CompileTimeErrorCode.ASSIGNMENT_TO_FINAL_LOCAL,
arguments: [element.name!],
);
}
}
}
}
}
}
}