| // 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/syntactic_entity.dart'; | |
| import 'package:analyzer/dart/ast/token.dart'; | |
| import 'package:analyzer/dart/element/type.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/resolution_result.dart'; | |
| import 'package:analyzer/src/dart/resolver/type_analyzer.dart'; | |
| import 'package:analyzer/src/dart/resolver/type_property_resolver.dart'; | |
| import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; | |
| import 'package:analyzer/src/error/listener.dart'; | |
| import 'package:analyzer/src/generated/super_context.dart'; | |
| /// Helper for resolving binary expressions. | |
| class BinaryExpressionResolver { | |
| final TypeAnalyzer _typeAnalyzer; | |
| final TypePropertyResolver _typePropertyResolver; | |
| BinaryExpressionResolver({required TypeAnalyzer typeAnalyzer}) | |
| : _typeAnalyzer = typeAnalyzer, | |
| _typePropertyResolver = typeAnalyzer.typePropertyResolver; | |
| DiagnosticReporter get _diagnosticReporter => | |
| _typeAnalyzer.diagnosticReporter; | |
| TypeProviderImpl get _typeProvider => _typeAnalyzer.typeProvider; | |
| TypeSystemImpl get _typeSystem => _typeAnalyzer.typeSystem; | |
| void resolveBinaryOperatorInvocation( | |
| BinaryOperatorInvocationImpl node, { | |
| required TypeImpl contextType, | |
| }) { | |
| var operator = node.operator.type; | |
| if (operator == TokenType.BANG_EQ || operator == TokenType.EQ_EQ) { | |
| _resolveEqual(node, notEqual: operator == TokenType.BANG_EQ); | |
| 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) { | |
| _diagnosticReporter.report( | |
| diag.notBinaryOperator | |
| .withArguments(name: operator.lexeme) | |
| .at(node.operator), | |
| ); | |
| } | |
| _resolveUnsupportedOperator(node); | |
| } | |
| void resolveIfNull(IfNullImpl node, {required TypeImpl contextType}) { | |
| var left = node.leftOperand; | |
| var right = node.rightOperand; | |
| var flow = _typeAnalyzer.flowAnalysis.flow; | |
| // An if-null expression `E` of the form `e1 ?? e2` with context type `K` is | |
| // analyzed as follows: | |
| // | |
| // - Let `T1` be the type of `e1` inferred with context type `K?`. | |
| _typeAnalyzer.analyzeExpression( | |
| left, | |
| SharedTypeSchemaView(_typeSystem.makeNullable(contextType)), | |
| ); | |
| left = _typeAnalyzer.popRewrite()!; | |
| var t1 = _typeAnalyzer.instanceReceiverType(left); | |
| // - Let `T2` be the type of `e2` inferred with context type `J`, where: | |
| // - If `K` is `_`, `J = T1`. | |
| TypeImpl j; | |
| if (contextType is DynamicType || | |
| contextType is InvalidType || | |
| contextType is UnknownInferredType) { | |
| j = t1; | |
| } else | |
| // - Otherwise, `J = K`. | |
| { | |
| j = contextType; | |
| } | |
| flow?.ifNullExpression_rightBegin( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(left), | |
| SharedTypeView(t1), | |
| offset: node.operator.offset, | |
| ); | |
| _typeAnalyzer.analyzeExpression(right, SharedTypeSchemaView(j)); | |
| right = _typeAnalyzer.popRewrite()!; | |
| flow?.ifNullExpression_end(offset: node.end); | |
| var t2 = right.typeOrThrow; | |
| // - 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 = _typeAnalyzer.operations | |
| .greatestClosureOfSchema(SharedTypeSchemaView(contextType)) | |
| .unwrapTypeView<TypeImpl>(); | |
| DartType staticType; | |
| // If `inferenceUpdate3` is not enabled, then the type of `E` is `T`. | |
| if (!_typeAnalyzer.definingLibrary.featureSet.isEnabled( | |
| Feature.inference_update_3, | |
| )) { | |
| staticType = t; | |
| } else | |
| // - If `T <: S`, then the type of `E` is `T`. | |
| if (_typeSystem.isSubtypeOf(t, s)) { | |
| staticType = 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)) { | |
| staticType = s; | |
| } else | |
| // - Otherwise, the type of `E` is `T`. | |
| { | |
| staticType = t; | |
| } | |
| node.recordStaticType(staticType, typeAnalyzer: _typeAnalyzer); | |
| _typeAnalyzer.checkForArgumentTypeNotAssignableForArgument(right); | |
| } | |
| void resolveLogicalAnd(LogicalAndImpl node) { | |
| _resolveLogicalExpression( | |
| node, | |
| leftOperand: node.leftOperand, | |
| rightOperand: node.rightOperand, | |
| isAnd: true, | |
| operatorOffset: node.operator.offset, | |
| ); | |
| } | |
| void resolveLogicalOr(LogicalOrImpl node) { | |
| _resolveLogicalExpression( | |
| node, | |
| leftOperand: node.leftOperand, | |
| rightOperand: node.rightOperand, | |
| isAnd: false, | |
| operatorOffset: node.operator.offset, | |
| ); | |
| } | |
| void _checkNonBoolOperand( | |
| Expression operand, | |
| String operator, { | |
| required Map<SharedTypeView, NonPromotionReason> Function()? whyNotPromoted, | |
| }) { | |
| _typeAnalyzer.boolExpressionVerifier.checkForNonBoolExpression( | |
| operand, | |
| locatableDiagnostic: diag.nonBoolOperand.withArguments( | |
| operator: operator, | |
| ), | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| } | |
| void _resolveEqual( | |
| BinaryOperatorInvocationImpl node, { | |
| required bool notEqual, | |
| }) { | |
| var leftOperand = node.leftOperand; | |
| var left = _typeAnalyzer.analyzeInstanceReceiver(leftOperand); | |
| var flowAnalysis = _typeAnalyzer.flowAnalysis; | |
| var flow = flowAnalysis.flow; | |
| ExpressionInfo? leftInfo; | |
| var leftExtensionOverride = left is ExtensionOverride2; | |
| if (left is ExpressionImpl && !leftExtensionOverride) { | |
| leftInfo = flowAnalysis.getExpressionInfo(left); | |
| } else if (left is SuperReference) { | |
| leftInfo = flow?.superExpression(); | |
| } | |
| // When evaluating exactly a dot shorthand in the RHS, we save the LHS type | |
| // to provide the context type for the shorthand. | |
| var leftType = left is ExpressionImpl ? left.staticType : null; | |
| if (leftType != null && _typeAnalyzer.isDotShorthand(node.rightOperand)) { | |
| _typeAnalyzer.pushDotShorthandContext( | |
| node.rightOperand, | |
| SharedTypeSchemaView(leftType), | |
| ); | |
| } | |
| _typeAnalyzer.analyzeExpression( | |
| node.rightOperand, | |
| SharedTypeSchemaView(UnknownInferredType.instance), | |
| ); | |
| var right = _typeAnalyzer.popRewrite()!; | |
| var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted( | |
| flowAnalysis.getExpressionInfo(right), | |
| ); | |
| if (!leftExtensionOverride && flow != null) { | |
| flowAnalysis.storeExpressionInfo( | |
| node, | |
| flow.equalityOperation_end( | |
| leftInfo, | |
| SharedTypeView(_typeAnalyzer.instanceReceiverType(left)), | |
| flowAnalysis.getExpressionInfo(right), | |
| SharedTypeView(right.typeOrThrow), | |
| notEqual: notEqual, | |
| ), | |
| ); | |
| } | |
| _resolveUserDefinableElement( | |
| node, | |
| TokenType.EQ_EQ.lexeme, | |
| promoteLeftTypeToNonNull: true, | |
| ); | |
| _resolveUserDefinableType(node); | |
| _typeAnalyzer.checkForArgumentTypeNotAssignableForArgument( | |
| node.rightOperand, | |
| promoteParameterToNullable: true, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| void reportNullComparison(SyntacticEntity start, SyntacticEntity end) { | |
| var diagnosticCode = notEqual | |
| ? diag.unnecessaryNullComparisonAlwaysNullFalse | |
| : diag.unnecessaryNullComparisonAlwaysNullTrue; | |
| var offset = start.offset; | |
| _diagnosticReporter.report( | |
| diagnosticCode.atOffset(offset: offset, length: end.end - offset), | |
| ); | |
| } | |
| PromotableElementImpl? unassignedElement(InstanceReceiverImpl expression) { | |
| var element = switch (expression) { | |
| UnqualifiedNameExpressionImpl( | |
| resolution: VariableReadResolutionImpl(:var element), | |
| ) => | |
| element, | |
| _ => null, | |
| }; | |
| if (expression is ExpressionImpl && | |
| element is PromotableElementImpl && | |
| flowAnalysis.isDefinitelyUnassigned(expression, element)) { | |
| return element; | |
| } | |
| return null; | |
| } | |
| if (right is NullLiteralImpl) { | |
| if (unassignedElement(left) != null) { | |
| reportNullComparison(left, node.operator); | |
| } | |
| } else if (left is NullLiteralImpl) { | |
| if (unassignedElement(right) != null) { | |
| reportNullComparison(node.operator, right); | |
| } | |
| } | |
| } | |
| void _resolveLogicalExpression( | |
| ExpressionImpl node, { | |
| required ExpressionImpl leftOperand, | |
| required ExpressionImpl rightOperand, | |
| required bool isAnd, | |
| required int operatorOffset, | |
| }) { | |
| var left = leftOperand; | |
| var right = rightOperand; | |
| var flow = _typeAnalyzer.flowAnalysis.flow; | |
| flow?.logicalBinaryOp_begin(offset: node.offset); | |
| _typeAnalyzer.analyzeExpression( | |
| left, | |
| SharedTypeSchemaView(_typeProvider.boolType), | |
| ); | |
| left = _typeAnalyzer.popRewrite()!; | |
| var leftWhyNotPromoted = _typeAnalyzer.flowAnalysis.flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(left), | |
| ); | |
| flow?.logicalBinaryOp_rightBegin( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(left), | |
| node, | |
| isAnd: isAnd, | |
| offset: operatorOffset, | |
| ); | |
| _typeAnalyzer.checkUnreachableNode(right); | |
| _typeAnalyzer.analyzeExpression( | |
| right, | |
| SharedTypeSchemaView(_typeProvider.boolType), | |
| ); | |
| right = _typeAnalyzer.popRewrite()!; | |
| var rightWhyNotPromoted = _typeAnalyzer.flowAnalysis.flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(right), | |
| ); | |
| _typeAnalyzer.nullSafetyDeadCodeVerifier.flowEnd(right); | |
| _typeAnalyzer.flowAnalysis.storeExpressionInfo( | |
| node, | |
| flow?.logicalBinaryOp_end( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(right), | |
| isAnd: isAnd, | |
| offset: node.end, | |
| ), | |
| ); | |
| var operator = isAnd ? '&&' : '||'; | |
| _checkNonBoolOperand(left, operator, whyNotPromoted: leftWhyNotPromoted); | |
| _checkNonBoolOperand(right, operator, whyNotPromoted: rightWhyNotPromoted); | |
| node.recordStaticType(_typeProvider.boolType, typeAnalyzer: _typeAnalyzer); | |
| } | |
| void _resolveRightOperand( | |
| BinaryOperatorInvocationImpl node, | |
| TypeImpl contextType, | |
| ) { | |
| var left = node.leftOperand; | |
| var invokeType = node.element?.type; | |
| TypeImpl rightContextType; | |
| if (invokeType != null && invokeType.formalParameters.isNotEmpty) { | |
| // If this is a user-defined operator, set the right operand context | |
| // using the operator method's parameter type. | |
| var rightParam = invokeType.formalParameters[0]; | |
| rightContextType = _typeSystem.refineNumericInvocationContext( | |
| left is ExpressionImpl | |
| ? left.staticType | |
| : _typeAnalyzer.instanceReceiverType(left), | |
| node.element, | |
| contextType, | |
| rightParam.type, | |
| ); | |
| } else { | |
| rightContextType = UnknownInferredType.instance; | |
| } | |
| _typeAnalyzer.analyzeExpression( | |
| node.rightOperand, | |
| SharedTypeSchemaView(rightContextType), | |
| ); | |
| var right = _typeAnalyzer.popRewrite()!; | |
| var whyNotPromoted = _typeAnalyzer.flowAnalysis.flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(right), | |
| ); | |
| _resolveUserDefinableType(node); | |
| _typeAnalyzer.checkForArgumentTypeNotAssignableForArgument( | |
| right, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| } | |
| void _resolveUnsupportedOperator(BinaryOperatorInvocationImpl node) { | |
| _typeAnalyzer.analyzeInstanceReceiver(node.leftOperand); | |
| _typeAnalyzer.analyzeExpression( | |
| node.rightOperand, | |
| _typeAnalyzer.operations.unknownType, | |
| ); | |
| _typeAnalyzer.popRewrite(); | |
| node.recordStaticType( | |
| InvalidTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| } | |
| void _resolveUserDefinable( | |
| BinaryOperatorInvocationImpl node, { | |
| required TypeImpl contextType, | |
| }) { | |
| var left = node.leftOperand; | |
| left = _typeAnalyzer.analyzeInstanceReceiver(left); | |
| if (left is SuperReferenceImpl) { | |
| if (SuperContext.of(left) != SuperContext.valid) { | |
| _typeAnalyzer.analyzeExpression( | |
| node.rightOperand, | |
| SharedTypeSchemaView(InvalidTypeImpl.instance), | |
| ); | |
| _typeAnalyzer.popRewrite(); | |
| node.recordStaticType( | |
| InvalidTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| } | |
| } | |
| var operator = node.operator; | |
| _resolveUserDefinableElement(node, operator.lexeme); | |
| _resolveRightOperand(node, contextType); | |
| } | |
| void _resolveUserDefinableElement( | |
| BinaryOperatorInvocationImpl node, | |
| String methodName, { | |
| bool promoteLeftTypeToNonNull = false, | |
| }) { | |
| var leftOperand = node.leftOperand; | |
| if (leftOperand is ExtensionOverride2Impl) { | |
| var extension = leftOperand.element; | |
| 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`. | |
| _diagnosticReporter.report( | |
| diag.undefinedExtensionOperator | |
| .withArguments( | |
| operator: methodName, | |
| extensionName: extension.name!, | |
| ) | |
| .at(node.operator), | |
| ); | |
| } | |
| node.element = member; | |
| return; | |
| } | |
| var leftType = _typeAnalyzer.instanceReceiverType(leftOperand); | |
| if (identical(leftType, NeverTypeImpl.instance)) { | |
| _typeAnalyzer.diagnosticReporter.report( | |
| diag.receiverOfTypeNever.at(leftOperand), | |
| ); | |
| return; | |
| } | |
| if (promoteLeftTypeToNonNull) { | |
| leftType = _typeSystem.promoteToNonNull(leftType); | |
| } | |
| ResolutionResult result = _typePropertyResolver.resolve( | |
| receiver: leftOperand, | |
| receiverType: leftType, | |
| name: methodName, | |
| hasRead: true, | |
| hasWrite: false, | |
| propertyErrorEntity: node.operator, | |
| nameErrorEntity: node, | |
| ); | |
| node.element = result.getter2 as InternalMethodElement?; | |
| if (result.getterOutcome == LookupOutcome.notFound) { | |
| if (leftOperand is SuperReference) { | |
| _diagnosticReporter.report( | |
| diag.undefinedSuperOperator | |
| .withArguments(operator: methodName, type: leftType) | |
| .at(node.operator), | |
| ); | |
| } else { | |
| _diagnosticReporter.report( | |
| diag.undefinedOperator | |
| .withArguments(operator: methodName, type: leftType) | |
| .at(node.operator), | |
| ); | |
| } | |
| } | |
| } | |
| void _resolveUserDefinableType(BinaryOperatorInvocationImpl node) { | |
| var leftOperand = node.leftOperand; | |
| TypeImpl leftType; | |
| if (leftOperand is ExtensionOverride2Impl) { | |
| leftType = leftOperand.extendedType!; | |
| } else { | |
| leftType = _typeAnalyzer.instanceReceiverType(leftOperand); | |
| leftType = _typeSystem.resolveToBound(leftType); | |
| } | |
| if (identical(leftType, NeverTypeImpl.instance)) { | |
| node.recordStaticType( | |
| NeverTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| } | |
| var staticType = node.element?.returnType; | |
| if (node.operator.type == TokenType.EQ_EQ) { | |
| staticType = _typeSystem.typeProvider.boolType; | |
| } else if (leftType is DynamicType) { | |
| staticType ??= DynamicTypeImpl.instance; | |
| } else { | |
| staticType ??= InvalidTypeImpl.instance; | |
| } | |
| if (leftOperand is! ExtensionOverride2) { | |
| staticType = _typeSystem.refineBinaryExpressionType( | |
| leftType, | |
| node.operator.type, | |
| node.rightOperand.typeOrThrow, | |
| staticType, | |
| node.element, | |
| ); | |
| } | |
| node.recordStaticType(staticType, typeAnalyzer: _typeAnalyzer); | |
| } | |
| } |