| // 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!], |
| ); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |