| // 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/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_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'; | |
| /// Helper for resolving V2 assignment expressions and their targets. | |
| class AssignmentExpressionResolver { | |
| final TypeAnalyzer _typeAnalyzer; | |
| final TypePropertyResolver _typePropertyResolver; | |
| final AssignmentExpressionShared _assignmentShared; | |
| AssignmentExpressionResolver({required TypeAnalyzer typeAnalyzer}) | |
| : _typeAnalyzer = typeAnalyzer, | |
| _typePropertyResolver = typeAnalyzer.typePropertyResolver, | |
| _assignmentShared = AssignmentExpressionShared( | |
| typeAnalyzer: typeAnalyzer, | |
| ); | |
| DiagnosticReporter get _diagnosticReporter => | |
| _typeAnalyzer.diagnosticReporter; | |
| TypeSystemImpl get _typeSystem => _typeAnalyzer.typeSystem; | |
| void analyzePropertyTargetReceiver( | |
| AstNode node, | |
| ReceiverPropertyAssignmentTargetImpl target, | |
| ) { | |
| var receiver = target.receiver; | |
| switch (receiver) { | |
| case StaticQualifierImpl(): | |
| return; | |
| case SuperReferenceImpl(): | |
| _typeAnalyzer.visitSuperReference(receiver); | |
| if (target.operator.type == TokenType.QUESTION_PERIOD) { | |
| _typeAnalyzer.startNullAwareAssignmentTarget( | |
| receiver, | |
| offset: target.operator.offset, | |
| ); | |
| } | |
| return; | |
| case ExtensionOverride2Impl(): | |
| _typeAnalyzer.visitExtensionOverride2(receiver); | |
| receiver.legacyStaticType = | |
| receiver.extendedType ?? InvalidTypeImpl.instance; | |
| if (target.operator.type == TokenType.QUESTION_PERIOD) { | |
| _typeAnalyzer.startNullAwareAssignmentTarget( | |
| receiver, | |
| offset: target.operator.offset, | |
| ); | |
| } | |
| return; | |
| case ExpressionImpl(): | |
| _typeAnalyzer.analyzeExpression( | |
| receiver, | |
| SharedTypeSchemaView(UnknownInferredType.instance), | |
| continueNullShorting: true, | |
| ); | |
| receiver = _typeAnalyzer.popRewrite()!; | |
| target.receiver = receiver; | |
| var receiverDoesNotComplete = identical( | |
| _typeSystem.resolveToBound(receiver.typeOrThrow), | |
| NeverTypeImpl.instance, | |
| ); | |
| if (target.operator.type == TokenType.QUESTION_PERIOD && | |
| !receiverDoesNotComplete) { | |
| _typeAnalyzer.startNullAwareAssignmentTarget( | |
| receiver, | |
| offset: target.operator.offset, | |
| ); | |
| _typeAnalyzer.nullSafetyDeadCodeVerifier.recordDeadIntervalAt( | |
| node, | |
| target.name, | |
| ); | |
| _typeAnalyzer.nullSafetyDeadCodeVerifier.verifyNullAwareAccess( | |
| node, | |
| receiver, | |
| target.operator, | |
| ); | |
| } | |
| } | |
| } | |
| void resolveCompound( | |
| CompoundAssignmentImpl node, { | |
| required TypeImpl contextType, | |
| }) { | |
| TypeImpl readType; | |
| TypeImpl writeAcceptedType; | |
| InternalVariableElement? variableElement; | |
| switch (resolveReadWriteTarget(node, node.target)) { | |
| case ResolvedReadWriteTarget result: | |
| readType = result.readType; | |
| writeAcceptedType = result.writeAcceptedType; | |
| variableElement = result.variableElement; | |
| case UnresolvedReadWriteTarget(): | |
| _analyzeValue(node, UnknownInferredType.instance); | |
| node.operatorResultType = NeverTypeImpl.instance; | |
| node.recordStaticType( | |
| NeverTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| case InvalidReadWriteTarget(:var expression?): | |
| _resolveInvalidCompound(node, expression); | |
| return; | |
| case InvalidReadWriteTarget(): | |
| _analyzeValue(node, InvalidTypeImpl.instance); | |
| node.operatorResultType = InvalidTypeImpl.instance; | |
| node.recordStaticType( | |
| InvalidTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| } | |
| _resolveCompoundOperator(node, receiver: null, readType: readType); | |
| // Analyze `target op= value` as an operator invocation whose receiver has | |
| // the target's read type and whose surrounding context is the target's | |
| // write type. Flow analysis may provide a promoted write type for a | |
| // variable; other targets use the type accepted by their write resolution. | |
| var writeContextType = writeAcceptedType; | |
| if (variableElement case var element?) { | |
| writeContextType = _typeAnalyzer.localVariableTypeProvider.getWriteType( | |
| element, | |
| ); | |
| } | |
| var rhsContext = _computeCompoundRhsContext( | |
| operatorTargetType: readType, | |
| writeContextType: writeContextType, | |
| element: node.element, | |
| ); | |
| _typeAnalyzer.analyzeExpression( | |
| node.value, | |
| SharedTypeSchemaView(rhsContext), | |
| ); | |
| node.value = _typeAnalyzer.popRewrite()!; | |
| var whyNotPromoted = _typeAnalyzer.flowAnalysis.flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(node.value), | |
| ); | |
| var operatorResultType = _computeCompoundOperatorResultType( | |
| node, | |
| readType: readType, | |
| ); | |
| node.operatorResultType = operatorResultType; | |
| node.recordStaticType(operatorResultType, typeAnalyzer: _typeAnalyzer); | |
| _checkForInvalidAssignment( | |
| writeAcceptedType, | |
| node.value, | |
| operatorResultType, | |
| whyNotPromoted: null, | |
| ); | |
| _typeAnalyzer.checkForArgumentTypeNotAssignableForArgument( | |
| node.value, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| var flow = _typeAnalyzer.flowAnalysis.flow; | |
| if (flow == null) return; | |
| if (variableElement case PromotableElementImpl element) { | |
| _typeAnalyzer.flowAnalysis.storeExpressionInfo( | |
| node, | |
| flow.write( | |
| node, | |
| element, | |
| SharedTypeView(operatorResultType), | |
| null, | |
| offset: node.end, | |
| ), | |
| ); | |
| } | |
| } | |
| void resolveDirect( | |
| DirectAssignmentImpl node, { | |
| required TypeImpl contextType, | |
| }) { | |
| var target = node.target; | |
| late TypeImpl writeAcceptedType; | |
| InternalVariableElement? variableElement; | |
| switch (target) { | |
| case CascadeIndexAssignmentTargetImpl(): | |
| var result = _typeAnalyzer.resolveCascadeIndex( | |
| target, | |
| hasRead: false, | |
| hasWrite: true, | |
| ); | |
| var resolution = result?.write; | |
| target.write = resolution; | |
| _typeAnalyzer.analyzeExpression( | |
| target.index, | |
| SharedTypeSchemaView( | |
| resolution?.indexContextType ?? UnknownInferredType.instance, | |
| ), | |
| ); | |
| target.index = _typeAnalyzer.popRewrite()!; | |
| var whyNotPromoted = _typeAnalyzer.flowAnalysis.flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(target.index), | |
| ); | |
| var writeElement = resolution?.elementOrRecovery; | |
| _typeAnalyzer.checkIndexExpressionIndex( | |
| target.index, | |
| readElement: null, | |
| writeElement: writeElement, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| if (resolution == null) { | |
| _typeAnalyzer.analyzeExpression( | |
| node.value, | |
| SharedTypeSchemaView(UnknownInferredType.instance), | |
| ); | |
| node.value = _typeAnalyzer.popRewrite()!; | |
| node.recordStaticType( | |
| NeverTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| } | |
| writeAcceptedType = resolution.acceptedType; | |
| case CascadePropertyAssignmentTargetImpl(): | |
| var result = _typeAnalyzer.resolveCascadeProperty( | |
| node, | |
| target.name, | |
| hasRead: false, | |
| hasWrite: true, | |
| ); | |
| var resolution = result?.write; | |
| target.write = resolution; | |
| if (resolution == null) { | |
| _typeAnalyzer.analyzeExpression( | |
| node.value, | |
| SharedTypeSchemaView(UnknownInferredType.instance), | |
| ); | |
| node.value = _typeAnalyzer.popRewrite()!; | |
| node.recordStaticType( | |
| NeverTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| } | |
| writeAcceptedType = resolution.acceptedType; | |
| case ReceiverIndexAssignmentTargetImpl(): | |
| target.receiver = _typeAnalyzer.analyzeInstanceReceiver( | |
| target.receiver, | |
| continueNullShorting: true, | |
| ); | |
| var receiverDoesNotComplete = | |
| target.receiver is! ExtensionOverride2Impl && | |
| identical( | |
| _typeSystem.resolveToBound( | |
| _typeAnalyzer.instanceReceiverType(target.receiver), | |
| ), | |
| NeverTypeImpl.instance, | |
| ); | |
| if (target.question case var question? when !receiverDoesNotComplete) { | |
| _typeAnalyzer.startNullAwareAssignmentTarget( | |
| target.receiver, | |
| offset: question.offset, | |
| ); | |
| _typeAnalyzer.nullSafetyDeadCodeVerifier.visitNode(target.index); | |
| } | |
| var resolution = _typeAnalyzer.resolveIndexDirectAssignmentTarget( | |
| target, | |
| ); | |
| target.write = resolution; | |
| _typeAnalyzer.analyzeExpression( | |
| target.index, | |
| SharedTypeSchemaView( | |
| resolution?.indexContextType ?? UnknownInferredType.instance, | |
| ), | |
| ); | |
| target.index = _typeAnalyzer.popRewrite()!; | |
| var whyNotPromoted = _typeAnalyzer.flowAnalysis.flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(target.index), | |
| ); | |
| var writeElement = resolution?.elementOrRecovery; | |
| _typeAnalyzer.checkIndexExpressionIndex( | |
| target.index, | |
| readElement: null, | |
| writeElement: writeElement, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| if (resolution == null) { | |
| _typeAnalyzer.analyzeExpression( | |
| node.value, | |
| SharedTypeSchemaView(UnknownInferredType.instance), | |
| ); | |
| node.value = _typeAnalyzer.popRewrite()!; | |
| node.recordStaticType( | |
| NeverTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| } | |
| writeAcceptedType = resolution.acceptedType; | |
| case ReceiverPropertyAssignmentTargetImpl(): | |
| analyzePropertyTargetReceiver(node, target); | |
| var resolution = _typeAnalyzer | |
| .resolveReceiverPropertyDirectAssignmentTarget(target); | |
| target.write = resolution; | |
| if (resolution == null) { | |
| _typeAnalyzer.analyzeExpression( | |
| node.value, | |
| SharedTypeSchemaView(UnknownInferredType.instance), | |
| ); | |
| node.value = _typeAnalyzer.popRewrite()!; | |
| node.recordStaticType( | |
| NeverTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| } | |
| writeAcceptedType = resolution.acceptedType; | |
| case ImportPrefixedAssignmentTargetImpl(): | |
| _typeAnalyzer.resolveImportPrefixedAssignmentTarget(target); | |
| writeAcceptedType = target.write!.acceptedType; | |
| case UnqualifiedNameAssignmentTargetImpl(): | |
| var resolution = _typeAnalyzer.resolveUnqualifiedNameAssignmentTarget( | |
| target, | |
| ); | |
| target.write = resolution; | |
| writeAcceptedType = resolution.acceptedType; | |
| if (resolution case VariableWriteResolutionImpl(:var element)) { | |
| variableElement = element; | |
| } | |
| _assignmentShared.checkFinalTargetAlreadyAssigned(target); | |
| case ParsedAssignmentTargetImpl(): | |
| throw StateError('Parsed assignment target was not lowered'); | |
| case InvalidExtensionOverrideAssignmentTargetImpl(): | |
| _resolveInvalidExtensionOverride(node, target); | |
| return; | |
| case InvalidSuperAssignmentTargetImpl(): | |
| _resolveInvalidSuper(node, target); | |
| return; | |
| case InvalidExpressionAssignmentTargetImpl(): | |
| _resolveInvalidDirect(node, target); | |
| return; | |
| } | |
| var rhsContext = writeAcceptedType; | |
| if (variableElement case var element?) { | |
| rhsContext = _typeAnalyzer.localVariableTypeProvider.getWriteType( | |
| element, | |
| ); | |
| } | |
| _typeAnalyzer.analyzeExpression( | |
| node.value, | |
| SharedTypeSchemaView(rhsContext), | |
| ); | |
| node.value = _typeAnalyzer.popRewrite()!; | |
| var valueType = node.value.typeOrThrow; | |
| var flow = _typeAnalyzer.flowAnalysis.flow; | |
| var whyNotPromoted = flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(node.value), | |
| ); | |
| node.recordStaticType(valueType, typeAnalyzer: _typeAnalyzer); | |
| _checkForInvalidAssignment( | |
| writeAcceptedType, | |
| node.value, | |
| valueType, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| if (flow == null) return; | |
| if (variableElement case PromotableElementImpl element) { | |
| _typeAnalyzer.flowAnalysis.storeExpressionInfo( | |
| node, | |
| flow.write( | |
| node, | |
| element, | |
| SharedTypeView(node.typeOrThrow), | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(node.value), | |
| offset: node.end, | |
| ), | |
| ); | |
| } | |
| } | |
| void resolveIfNull( | |
| IfNullAssignmentImpl node, { | |
| required TypeImpl contextType, | |
| }) { | |
| TypeImpl readType; | |
| TypeImpl writeAcceptedType; | |
| InternalVariableElement? variableElement; | |
| ExpressionInfo? readExpressionInfo; | |
| switch (resolveReadWriteTarget(node, node.target)) { | |
| case ResolvedReadWriteTarget result: | |
| readType = result.readType; | |
| writeAcceptedType = result.writeAcceptedType; | |
| variableElement = result.variableElement; | |
| readExpressionInfo = result.readExpressionInfo; | |
| case UnresolvedReadWriteTarget(): | |
| _analyzeValue(node, UnknownInferredType.instance); | |
| node.recordStaticType( | |
| NeverTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| case InvalidReadWriteTarget(:var expression?): | |
| _resolveInvalidIfNull(node, expression, contextType: contextType); | |
| return; | |
| case InvalidReadWriteTarget(:var target): | |
| _analyzeValue(node, InvalidTypeImpl.instance); | |
| node.recordStaticType( | |
| target is InvalidExtensionOverrideAssignmentTargetImpl | |
| ? DynamicTypeImpl.instance | |
| : InvalidTypeImpl.instance, | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| return; | |
| } | |
| if (readType is VoidType) { | |
| _diagnosticReporter.report(diag.useOfVoidResult.at(node.operator)); | |
| } | |
| var rhsContext = writeAcceptedType; | |
| if (variableElement case var element?) { | |
| rhsContext = _typeAnalyzer.localVariableTypeProvider.getWriteType( | |
| element, | |
| ); | |
| } | |
| var flow = _typeAnalyzer.flowAnalysis.flow; | |
| flow?.ifNullExpression_rightBegin( | |
| readExpressionInfo, | |
| SharedTypeView(readType), | |
| offset: node.operator.offset, | |
| ); | |
| _typeAnalyzer.analyzeExpression( | |
| node.value, | |
| SharedTypeSchemaView(rhsContext), | |
| ); | |
| node.value = _typeAnalyzer.popRewrite()!; | |
| var valueType = node.value.typeOrThrow; | |
| var whyNotPromoted = flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(node.value), | |
| ); | |
| var nodeType = _computeIfNullType( | |
| readType: readType, | |
| valueType: valueType, | |
| contextType: contextType, | |
| ); | |
| node.recordStaticType(nodeType, typeAnalyzer: _typeAnalyzer); | |
| _checkForInvalidAssignment( | |
| writeAcceptedType, | |
| node.value, | |
| valueType, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| if (flow == null) return; | |
| if (variableElement case PromotableElementImpl element) { | |
| _typeAnalyzer.flowAnalysis.storeExpressionInfo( | |
| node, | |
| flow.write( | |
| node, | |
| element, | |
| SharedTypeView(node.typeOrThrow), | |
| null, | |
| offset: node.end, | |
| ), | |
| ); | |
| } | |
| flow.ifNullExpression_end(offset: node.end); | |
| } | |
| /// Resolves [target] of the read-write operation [node]: a compound | |
| /// assignment, `??=`, `++`, or `--`. | |
| /// | |
| /// This does all the work that depends only on the target, so that each | |
| /// operation adds only its operator-specific work. | |
| ReadWriteTargetResult resolveReadWriteTarget( | |
| ExpressionImpl node, | |
| AssignmentTargetImpl target, | |
| ) { | |
| switch (target) { | |
| case CascadeIndexAssignmentTargetImpl(): | |
| var result = _resolveCascadeIndexReadWriteTarget(target); | |
| if (result == null) return const UnresolvedReadWriteTarget(); | |
| return ResolvedReadWriteTarget( | |
| readType: result.read.type, | |
| writeAcceptedType: result.write.acceptedType, | |
| ); | |
| case CascadePropertyAssignmentTargetImpl(): | |
| var result = _resolveCascadePropertyReadWriteTarget(node, target); | |
| if (result == null) return const UnresolvedReadWriteTarget(); | |
| return ResolvedReadWriteTarget( | |
| readType: operatorReadType(result.read, result.write), | |
| writeAcceptedType: result.write.acceptedType, | |
| readExpressionInfo: result.readExpressionInfo, | |
| ); | |
| case ReceiverIndexAssignmentTargetImpl(): | |
| var result = _resolveIndexReadWriteTarget(target); | |
| if (result == null) return const UnresolvedReadWriteTarget(); | |
| return ResolvedReadWriteTarget( | |
| readType: result.read.type, | |
| writeAcceptedType: result.write.acceptedType, | |
| ); | |
| case ReceiverPropertyAssignmentTargetImpl(): | |
| analyzePropertyTargetReceiver(node, target); | |
| var result = _typeAnalyzer | |
| .resolveReceiverPropertyReadWriteAssignmentTarget(target); | |
| if (result == null) return const UnresolvedReadWriteTarget(); | |
| target.read = result.read; | |
| target.write = result.write; | |
| return ResolvedReadWriteTarget( | |
| readType: operatorReadType(result.read, result.write), | |
| writeAcceptedType: result.write.acceptedType, | |
| readExpressionInfo: result.readExpressionInfo, | |
| ); | |
| case ImportPrefixedAssignmentTargetImpl(): | |
| _typeAnalyzer.resolveImportPrefixedAssignmentTarget(target); | |
| var read = target.read!; | |
| var write = target.write!; | |
| return ResolvedReadWriteTarget( | |
| readType: operatorReadType(read, write), | |
| writeAcceptedType: write.acceptedType, | |
| ); | |
| case UnqualifiedNameAssignmentTargetImpl(): | |
| var result = _typeAnalyzer | |
| .resolveUnqualifiedNameReadWriteAssignmentTarget(target); | |
| target.read = result.read; | |
| target.write = result.write; | |
| _assignmentShared.checkFinalTargetAlreadyAssigned(target); | |
| return ResolvedReadWriteTarget( | |
| readType: operatorReadType(result.read, result.write), | |
| writeAcceptedType: result.write.acceptedType, | |
| variableElement: switch (result.write) { | |
| VariableWriteResolutionImpl(:var element) => element, | |
| _ => null, | |
| }, | |
| readExpressionInfo: result.readExpressionInfo, | |
| ); | |
| case ParsedAssignmentTargetImpl(): | |
| throw StateError('Parsed assignment target was not lowered'); | |
| case InvalidExtensionOverrideAssignmentTargetImpl(): | |
| _typeAnalyzer.visitExtensionOverride2(target.extensionOverride); | |
| target.read = const InvalidReadResolutionImpl(); | |
| target.write = const InvalidWriteResolutionImpl(); | |
| return InvalidReadWriteTarget(target: target, expression: null); | |
| case InvalidSuperAssignmentTargetImpl(): | |
| _typeAnalyzer.visitSuperReference(target.superReference); | |
| target.read = const InvalidReadResolutionImpl(); | |
| target.write = const InvalidWriteResolutionImpl(); | |
| return InvalidReadWriteTarget(target: target, expression: null); | |
| case InvalidExpressionAssignmentTargetImpl(): | |
| _typeAnalyzer.analyzeExpression( | |
| target.expression, | |
| SharedTypeSchemaView(UnknownInferredType.instance), | |
| ); | |
| target.expression = _typeAnalyzer.popRewrite()!; | |
| target.read = const InvalidReadResolutionImpl(); | |
| target.write = const InvalidWriteResolutionImpl(); | |
| return InvalidReadWriteTarget( | |
| target: target, | |
| expression: target.expression, | |
| ); | |
| } | |
| } | |
| /// Analyzes the value of [node] with [contextType], and replaces it with | |
| /// its rewrite. | |
| void _analyzeValue(AssignmentExpression2Impl node, TypeImpl contextType) { | |
| _typeAnalyzer.analyzeExpression( | |
| node.value, | |
| SharedTypeSchemaView(contextType), | |
| ); | |
| node.value = _typeAnalyzer.popRewrite()!; | |
| } | |
| void _checkForInvalidAssignment( | |
| TypeImpl writeType, | |
| Expression right, | |
| TypeImpl rightType, { | |
| required Map<SharedTypeView, NonPromotionReason> Function()? whyNotPromoted, | |
| }) { | |
| if (writeType is! VoidType && _checkForUseOfVoidResult(right)) { | |
| return; | |
| } | |
| var strictCasts = _typeAnalyzer.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.report( | |
| diag.recordLiteralOnePositionalNoTrailingCommaByType.at(right), | |
| ); | |
| return; | |
| } | |
| } | |
| _diagnosticReporter.report( | |
| diag.invalidAssignment | |
| .withArguments( | |
| actualStaticType: rightType, | |
| expectedStaticType: writeType, | |
| ) | |
| .withContextMessages( | |
| _typeAnalyzer.computeWhyNotPromotedMessages( | |
| right, | |
| whyNotPromoted?.call(), | |
| ), | |
| ) | |
| .at(right), | |
| ); | |
| } | |
| /// 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 [diag.useOfVoidResult]. | |
| // TODO(scheglov): this is duplicate | |
| bool _checkForUseOfVoidResult(Expression expression) { | |
| if (expression.staticType is! VoidTypeImpl) { | |
| return false; | |
| } | |
| if (expression is NamedFunctionInvocation) { | |
| _diagnosticReporter.report(diag.useOfVoidResult.at(expression.name)); | |
| } else { | |
| _diagnosticReporter.report(diag.useOfVoidResult.at(expression)); | |
| } | |
| return true; | |
| } | |
| TypeImpl _computeCompoundOperatorResultType( | |
| CompoundAssignmentImpl node, { | |
| required TypeImpl readType, | |
| }) { | |
| if (identical(readType, NeverTypeImpl.instance)) { | |
| return NeverTypeImpl.instance; | |
| } | |
| if (readType is DynamicType) { | |
| return DynamicTypeImpl.instance; | |
| } | |
| var element = node.element; | |
| if (element == null) { | |
| return InvalidTypeImpl.instance; | |
| } | |
| return _typeSystem.refineBinaryExpressionType( | |
| readType, | |
| node.operator.type, | |
| node.value.typeOrThrow, | |
| element.returnType, | |
| element, | |
| ); | |
| } | |
| TypeImpl _computeCompoundRhsContext({ | |
| required TypeImpl operatorTargetType, | |
| required TypeImpl writeContextType, | |
| required InternalMethodElement? element, | |
| }) { | |
| if (element != null && element.formalParameters.isNotEmpty) { | |
| return _typeSystem.refineNumericInvocationContext( | |
| operatorTargetType, | |
| element, | |
| writeContextType, | |
| element.formalParameters.first.type, | |
| ); | |
| } | |
| return UnknownInferredType.instance; | |
| } | |
| TypeImpl _computeIfNullType({ | |
| required TypeImpl readType, | |
| required TypeImpl valueType, | |
| required TypeImpl contextType, | |
| }) { | |
| // An if-null assignment `E` of the form `lvalue ??= e` with context type | |
| // `K` is analyzed as follows: | |
| // | |
| // - Let `T1` be the read type of the lvalue. | |
| var t1 = readType; | |
| // - Let `T2` be the type of `e` inferred with context type `T1`. | |
| var t2 = valueType; | |
| // - 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>(); | |
| // If `inferenceUpdate3` is not enabled, then the type of `E` is `T`. | |
| if (!_typeAnalyzer.definingLibrary.featureSet.isEnabled( | |
| Feature.inference_update_3, | |
| )) { | |
| return t; | |
| } | |
| // - If `T <: S`, then the type of `E` is `T`. | |
| if (_typeSystem.isSubtypeOf(t, s)) { | |
| return t; | |
| } | |
| // - Otherwise, if `NonNull(T1) <: S` and `T2 <: S`, then the type of `E` | |
| // is `S`. | |
| if (_typeSystem.isSubtypeOf(nonNullT1, s) && | |
| _typeSystem.isSubtypeOf(t2, s)) { | |
| return s; | |
| } | |
| // - Otherwise, the type of `E` is `T`. | |
| return t; | |
| } | |
| ({IndexReadResolutionImpl read, IndexWriteResolutionImpl write})? | |
| _resolveCascadeIndexReadWriteTarget(CascadeIndexAssignmentTargetImpl target) { | |
| var result = _typeAnalyzer.resolveCascadeIndex( | |
| target, | |
| hasRead: true, | |
| hasWrite: true, | |
| ); | |
| target.read = result?.read; | |
| target.write = result?.write; | |
| _typeAnalyzer.analyzeExpression( | |
| target.index, | |
| SharedTypeSchemaView( | |
| result?.read?.indexContextType ?? UnknownInferredType.instance, | |
| ), | |
| ); | |
| target.index = _typeAnalyzer.popRewrite()!; | |
| var whyNotPromoted = _typeAnalyzer.flowAnalysis.flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(target.index), | |
| ); | |
| var readElement = result?.read?.elementOrRecovery; | |
| var writeElement = result?.write?.elementOrRecovery; | |
| _typeAnalyzer.checkIndexExpressionIndex( | |
| target.index, | |
| readElement: readElement, | |
| writeElement: writeElement, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| var read = result?.read; | |
| var write = result?.write; | |
| if (read == null || write == null) return null; | |
| return (read: read, write: write); | |
| } | |
| ({ | |
| NamedReadResolutionImpl read, | |
| NamedWriteResolutionImpl write, | |
| ExpressionInfo? readExpressionInfo, | |
| })? | |
| _resolveCascadePropertyReadWriteTarget( | |
| ExpressionImpl node, | |
| CascadePropertyAssignmentTargetImpl target, | |
| ) { | |
| var result = _typeAnalyzer.resolveCascadeProperty( | |
| node, | |
| target.name, | |
| hasRead: true, | |
| hasWrite: true, | |
| ); | |
| target.read = result?.read; | |
| target.write = result?.write; | |
| var read = result?.read; | |
| var write = result?.write; | |
| if (read == null || write == null) return null; | |
| return ( | |
| read: read, | |
| write: write, | |
| readExpressionInfo: result?.readExpressionInfo, | |
| ); | |
| } | |
| void _resolveCompoundOperator( | |
| CompoundAssignmentImpl node, { | |
| required ExpressionImpl? receiver, | |
| required TypeImpl readType, | |
| }) { | |
| if (identical(readType, NeverTypeImpl.instance)) { | |
| return; | |
| } | |
| if (readType is VoidType) { | |
| _diagnosticReporter.report(diag.useOfVoidResult.at(node.operator)); | |
| return; | |
| } | |
| var methodName = | |
| node.operator.type.binaryOperatorOfCompoundAssignment!.lexeme; | |
| var result = _typePropertyResolver.resolve( | |
| receiver: receiver, | |
| receiverType: readType, | |
| name: methodName, | |
| hasRead: true, | |
| hasWrite: true, | |
| propertyErrorEntity: node.operator, | |
| nameErrorEntity: node.operator, | |
| parentNode: node, | |
| ); | |
| node.element = result.getter2 as InternalMethodElement?; | |
| if (result.getterOutcome == LookupOutcome.notFound) { | |
| _diagnosticReporter.report( | |
| diag.undefinedOperator | |
| .withArguments(operator: methodName, type: readType) | |
| .at(node.operator), | |
| ); | |
| } | |
| } | |
| ({IndexReadResolutionImpl read, IndexWriteResolutionImpl write})? | |
| _resolveIndexReadWriteTarget(ReceiverIndexAssignmentTargetImpl target) { | |
| target.receiver = _typeAnalyzer.analyzeInstanceReceiver( | |
| target.receiver, | |
| continueNullShorting: true, | |
| ); | |
| var receiverDoesNotComplete = | |
| target.receiver is! ExtensionOverride2Impl && | |
| identical( | |
| _typeSystem.resolveToBound( | |
| _typeAnalyzer.instanceReceiverType(target.receiver), | |
| ), | |
| NeverTypeImpl.instance, | |
| ); | |
| if (target.question case var question? when !receiverDoesNotComplete) { | |
| _typeAnalyzer.startNullAwareAssignmentTarget( | |
| target.receiver, | |
| offset: question.offset, | |
| ); | |
| _typeAnalyzer.nullSafetyDeadCodeVerifier.visitNode(target.index); | |
| } | |
| var result = _typeAnalyzer.resolveIndexReadWriteAssignmentTarget(target); | |
| target.read = result?.read; | |
| target.write = result?.write; | |
| _typeAnalyzer.analyzeExpression( | |
| target.index, | |
| SharedTypeSchemaView( | |
| result?.read.indexContextType ?? UnknownInferredType.instance, | |
| ), | |
| ); | |
| target.index = _typeAnalyzer.popRewrite()!; | |
| var whyNotPromoted = _typeAnalyzer.flowAnalysis.flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(target.index), | |
| ); | |
| var readElement = result?.read.elementOrRecovery; | |
| var writeElement = result?.write.elementOrRecovery; | |
| _typeAnalyzer.checkIndexExpressionIndex( | |
| target.index, | |
| readElement: readElement, | |
| writeElement: writeElement, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| return result; | |
| } | |
| void _resolveInvalidCompound( | |
| CompoundAssignmentImpl node, | |
| ExpressionImpl expression, | |
| ) { | |
| var readType = expression.typeOrThrow; | |
| _resolveCompoundOperator(node, receiver: expression, readType: readType); | |
| var rhsContext = _computeCompoundRhsContext( | |
| operatorTargetType: readType, | |
| writeContextType: readType, | |
| element: node.element, | |
| ); | |
| _analyzeValue(node, rhsContext); | |
| var whyNotPromoted = _typeAnalyzer.flowAnalysis.flow?.whyNotPromoted( | |
| _typeAnalyzer.flowAnalysis.getExpressionInfo(node.value), | |
| ); | |
| var operatorResultType = _computeCompoundOperatorResultType( | |
| node, | |
| readType: readType, | |
| ); | |
| node.operatorResultType = operatorResultType; | |
| node.recordStaticType(operatorResultType, typeAnalyzer: _typeAnalyzer); | |
| _typeAnalyzer.checkForArgumentTypeNotAssignableForArgument( | |
| node.value, | |
| whyNotPromoted: whyNotPromoted, | |
| ); | |
| } | |
| void _resolveInvalidDirect( | |
| DirectAssignmentImpl node, | |
| InvalidExpressionAssignmentTargetImpl target, { | |
| bool expressionIsResolved = false, | |
| }) { | |
| if (!expressionIsResolved) { | |
| _typeAnalyzer.analyzeExpression( | |
| target.expression, | |
| SharedTypeSchemaView(UnknownInferredType.instance), | |
| ); | |
| target.expression = _typeAnalyzer.popRewrite()!; | |
| } | |
| target.write = const InvalidWriteResolutionImpl(); | |
| _typeAnalyzer.analyzeExpression( | |
| node.value, | |
| SharedTypeSchemaView(InvalidTypeImpl.instance), | |
| ); | |
| node.value = _typeAnalyzer.popRewrite()!; | |
| node.recordStaticType(node.value.typeOrThrow, typeAnalyzer: _typeAnalyzer); | |
| } | |
| void _resolveInvalidExtensionOverride( | |
| DirectAssignmentImpl node, | |
| InvalidExtensionOverrideAssignmentTargetImpl target, | |
| ) { | |
| _typeAnalyzer.visitExtensionOverride2(target.extensionOverride); | |
| target.write = const InvalidWriteResolutionImpl(); | |
| _analyzeValue(node, InvalidTypeImpl.instance); | |
| node.recordStaticType(node.value.typeOrThrow, typeAnalyzer: _typeAnalyzer); | |
| } | |
| void _resolveInvalidIfNull( | |
| IfNullAssignmentImpl node, | |
| ExpressionImpl expression, { | |
| required TypeImpl contextType, | |
| }) { | |
| var readType = expression.typeOrThrow; | |
| _analyzeValue(node, InvalidTypeImpl.instance); | |
| node.recordStaticType( | |
| _computeIfNullType( | |
| readType: readType, | |
| valueType: node.value.typeOrThrow, | |
| contextType: contextType, | |
| ), | |
| typeAnalyzer: _typeAnalyzer, | |
| ); | |
| } | |
| void _resolveInvalidSuper( | |
| DirectAssignmentImpl node, | |
| InvalidSuperAssignmentTargetImpl target, | |
| ) { | |
| _typeAnalyzer.visitSuperReference(target.superReference); | |
| target.write = const InvalidWriteResolutionImpl(); | |
| _analyzeValue(node, InvalidTypeImpl.instance); | |
| node.recordStaticType(node.value.typeOrThrow, typeAnalyzer: _typeAnalyzer); | |
| } | |
| /// The type that a read-write operation on a target reads, given the | |
| /// target's [read] and [write] resolutions. | |
| /// | |
| /// When the target names a method, the read is a tear-off, and the write | |
| /// fails, which is reported. The tear-off type is replaced with | |
| /// [InvalidTypeImpl], so that the operator, or the flow analysis and the | |
| /// dead code check of `??=`, don't report follow-up diagnostics about the | |
| /// method's function type. | |
| static TypeImpl operatorReadType( | |
| ReadResolutionImpl read, | |
| WriteResolutionImpl? write, | |
| ) { | |
| if (read is ExecutableTearOffResolution && | |
| write is InvalidNamedWriteResolution) { | |
| return InvalidTypeImpl.instance; | |
| } | |
| return read.type; | |
| } | |
| } | |
| class AssignmentExpressionShared { | |
| final TypeAnalyzer _typeAnalyzer; | |
| AssignmentExpressionShared({required TypeAnalyzer typeAnalyzer}) | |
| : _typeAnalyzer = typeAnalyzer; | |
| DiagnosticReporter get _errorReporter => _typeAnalyzer.diagnosticReporter; | |
| /// Reports a write to [target] if it is a final local variable that might | |
| /// already be assigned. | |
| void checkFinalTargetAlreadyAssigned( | |
| UnqualifiedNameAssignmentTargetImpl target, { | |
| bool isWrittenRepeatedly = false, | |
| }) { | |
| if (_typeAnalyzer.flowAnalysis.flow == null) return; | |
| var element = target.scopeLookupResult?.getter; | |
| if (element is PromotableElementImpl) { | |
| _checkFinalAlreadyAssigned( | |
| target, | |
| element, | |
| isWrittenRepeatedly: isWrittenRepeatedly, | |
| ); | |
| } | |
| } | |
| void _checkFinalAlreadyAssigned( | |
| AstNode node, | |
| PromotableElementImpl element, { | |
| required bool isWrittenRepeatedly, | |
| }) { | |
| var flowAnalysis = _typeAnalyzer.flowAnalysis; | |
| var assigned = flowAnalysis.isDefinitelyAssigned(node, element); | |
| var unassigned = flowAnalysis.isDefinitelyUnassigned(node, element); | |
| if (element.isFinal) { | |
| if (element.isLate) { | |
| if (isWrittenRepeatedly || assigned) { | |
| _errorReporter.report(diag.lateFinalLocalAlreadyAssigned.at(node)); | |
| } | |
| } else if (isWrittenRepeatedly || !unassigned) { | |
| _errorReporter.report( | |
| diag.assignmentToFinalLocal | |
| .withArguments(variableName: element.name!) | |
| .at(node), | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| /// An assignment target of a read-write operation that is analyzed as an | |
| /// expression, or as `super` or an extension override, because it can't be | |
| /// read and written. | |
| final class InvalidReadWriteTarget extends ReadWriteTargetResult { | |
| final AssignmentTargetImpl target; | |
| /// The analyzed expression, if [target] is an | |
| /// [InvalidExpressionAssignmentTargetImpl]. | |
| final ExpressionImpl? expression; | |
| InvalidReadWriteTarget({required this.target, required this.expression}); | |
| } | |
| /// The result of [AssignmentExpressionResolver.resolveReadWriteTarget]. | |
| sealed class ReadWriteTargetResult { | |
| const ReadWriteTargetResult(); | |
| } | |
| /// A resolved assignment target of a read-write operation. | |
| final class ResolvedReadWriteTarget extends ReadWriteTargetResult { | |
| /// The type of the value that the operation reads. | |
| final TypeImpl readType; | |
| /// The type that the value written back must be assignable to. | |
| final TypeImpl writeAcceptedType; | |
| /// The variable that is written, if the target is a local variable or a | |
| /// parameter. | |
| final InternalVariableElement? variableElement; | |
| /// The flow analysis information about the read. | |
| final ExpressionInfo? readExpressionInfo; | |
| ResolvedReadWriteTarget({ | |
| required this.readType, | |
| required this.writeAcceptedType, | |
| this.variableElement, | |
| this.readExpressionInfo, | |
| }); | |
| } | |
| /// An assignment target of a read-write operation whose resolution failed; | |
| /// the failure is already reported. | |
| final class UnresolvedReadWriteTarget extends ReadWriteTargetResult { | |
| const UnresolvedReadWriteTarget(); | |
| } |