blob: aa7bacef655b6f83227dc497a861bcca2aa8a97f [file] [log] [blame]
// Copyright (c) 2018, 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.
// TODO(jensj): Probably all `_createVariableGet(result)` needs their offset
// "nulled out".
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart';
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
as shared;
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
hide MapPatternEntry;
import 'package:_fe_analyzer_shared/src/util/link.dart';
import 'package:_fe_analyzer_shared/src/util/null_value.dart';
import 'package:_fe_analyzer_shared/src/util/stack_checker.dart';
import 'package:_fe_analyzer_shared/src/util/value_kind.dart';
import 'package:front_end/src/api_prototype/lowering_predicates.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/names.dart';
import 'package:kernel/src/legacy_erasure.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import '../../api_prototype/experimental_flags.dart';
import '../../base/instrumentation.dart'
show
InstrumentationValueForMember,
InstrumentationValueForType,
InstrumentationValueForTypeArgs;
import '../codes/fasta_codes.dart';
import '../kernel/body_builder.dart' show combineStatements;
import '../kernel/collections.dart'
show
ControlFlowElement,
ControlFlowMapEntry,
ForElement,
ForInElement,
ForInMapEntry,
ForMapEntry,
IfElement,
IfMapEntry,
SpreadElement,
SpreadMapEntry,
convertToElement;
import '../kernel/hierarchy/class_member.dart';
import '../kernel/implicit_type_argument.dart' show ImplicitTypeArgument;
import '../kernel/internal_ast.dart';
import '../kernel/late_lowering.dart' as late_lowering;
import '../problems.dart' as problems
show internalProblem, unhandled, unsupported;
import '../source/constructor_declaration.dart';
import '../source/source_library_builder.dart';
import '../uri_offset.dart';
import 'closure_context.dart';
import 'external_ast_helper.dart';
import 'for_in.dart';
import 'inference_helper.dart';
import 'inference_results.dart';
import 'inference_visitor_base.dart';
import 'object_access_target.dart';
import 'shared_type_analyzer.dart';
import 'stack_values.dart';
import 'type_constraint_gatherer.dart';
import 'type_inference_engine.dart';
import 'type_inferrer.dart' show TypeInferrerImpl;
import 'type_schema.dart' show UnknownType;
abstract class InferenceVisitor {
/// Performs type inference on the given [expression].
///
/// [typeContext] is the expected type of the expression, based on surrounding
/// code. [typeNeeded] indicates whether it is necessary to compute the
/// actual type of the expression. If [typeNeeded] is `true`,
/// [ExpressionInferenceResult.inferredType] is the actual type of the
/// expression; otherwise the [UnknownType].
///
/// Derived classes should override this method with logic that dispatches on
/// the expression type and calls the appropriate specialized "infer" method.
ExpressionInferenceResult inferExpression(
Expression expression, DartType typeContext,
{bool isVoidAllowed = false, bool forEffect = false});
/// Performs type inference on the given [statement].
///
/// If [closureContext] is not null, the [statement] is inferred using
/// [closureContext] as the current context.
StatementInferenceResult inferStatement(Statement statement,
[ClosureContext? closureContext]);
/// Performs type inference on the given [initializer].
InitializerInferenceResult inferInitializer(Initializer initializer);
}
class InferenceVisitorImpl extends InferenceVisitorBase
with
TypeAnalyzer<
TreeNode,
Statement,
Expression,
VariableDeclaration,
DartType,
Pattern,
InvalidExpression,
DartType,
StructuralParameter,
TypeDeclarationType,
TypeDeclaration>,
StackChecker
implements
ExpressionVisitor1<ExpressionInferenceResult, DartType>,
StatementVisitor<StatementInferenceResult>,
InitializerVisitor<InitializerInferenceResult>,
PatternVisitor1<PatternResult<DartType>, SharedMatchContext>,
InferenceVisitor {
/// Debug-only: if `true`, manipulations of [_rewriteStack] performed by
/// [popRewrite] and [pushRewrite] will be printed.
static const bool _debugRewriteStack = false;
Class? mapEntryClass;
@override
final OperationsCfe operations;
/// Context information for the current closure, or `null` if we are not
/// inside a closure.
ClosureContext? _closureContext;
/// If a switch statement is being visited and the type being switched on is a
/// (possibly nullable) enumerated type, the set of enum values for which no
/// case head has been seen yet; otherwise `null`.
///
/// Enum values are represented by the [Field] object they are desugared into.
/// If the type being switched on is nullable, then this set also includes a
/// value of `null` if no case head has been seen yet that handles `null`.
Set<Field?>? _enumFields;
/// Stack for obtaining rewritten expressions and statements. After
/// [dispatchExpression] or [dispatchStatement] visits a node for type
/// inference, the visited node (which may have been changed by the inference
/// process) is pushed onto this stack. Later, during the processing of the
/// enclosing node, the visited node is popped off the stack again, and the
/// enclosing node is updated to point to the new, rewritten node.
///
/// The stack sometimes contains `null`s. These account for situations where
/// it's necessary to push a value onto the stack to balance a later pop, but
/// there is no suitable expression or statement to push.
final List<Object> _rewriteStack = [];
@override
final TypeAnalyzerOptions options;
final ConstructorDeclaration? constructorDeclaration;
@override
late final SharedTypeAnalyzerErrors errors = new SharedTypeAnalyzerErrors(
visitor: this,
helper: helper,
uri: uriForInstrumentation,
coreTypes: coreTypes,
isNonNullableByDefault: isNonNullableByDefault);
/// The innermost cascade whose expressions are currently being visited, or
/// `null` if no cascade's expressions are currently being visited.
Cascade? _enclosingCascade;
/// Set to `true` when we are inside a try-statement or a local function.
///
/// This is used to optimize the encoding of [AssignedVariablePattern]. When
/// a pattern assignment occurs in a try block or a local function, a
/// partially matched pattern is observable, since exceptions occurring during
/// the matching can be caught.
// TODO(johnniwinther): This can be improved by detecting whether the assigned
// variable was declared outside the try statement or local function.
bool _inTryOrLocalFunction = false;
InferenceVisitorImpl(TypeInferrerImpl inferrer, InferenceHelper helper,
this.constructorDeclaration, this.operations)
: options = new TypeAnalyzerOptions(
nullSafetyEnabled: inferrer.libraryBuilder.isNonNullableByDefault,
patternsEnabled:
inferrer.libraryBuilder.libraryFeatures.patterns.isEnabled,
inferenceUpdate3Enabled: inferrer
.libraryBuilder.libraryFeatures.inferenceUpdate3.isEnabled),
super(inferrer, helper);
@override
int get stackHeight => _rewriteStack.length;
@override
Object? lookupStack(int index) =>
_rewriteStack[_rewriteStack.length - index - 1];
/// Used to report an internal error encountered in the stack listener.
@override
Never internalProblem(Message message, int charOffset, Uri uri) {
return problems.internalProblem(message, charOffset, uri);
}
/// Checks that [base] is a valid base stack height for a call to
/// [checkStack].
///
/// This can be used to initialize a stack base for subsequent calls to
/// [checkStack]. For instance:
///
/// int? stackBase;
/// // Set up the current stack height as the stack base.
/// assert(checkStackBase(node, stackBase = stackHeight));
/// ...
/// // Check that the stack is empty, relative to the stack base.
/// assert(checkStack(node, []));
///
/// or
///
/// int? stackBase;
/// // Assert that the current stack height is at least 4 and set
/// // the stack height - 4 up as the stack base.
/// assert(checkStackBase(node, stackBase = stackHeight - 4));
/// ...
/// // Check that the stack contains a single `Expression` element,
/// // relative to the stack base.
/// assert(checkStack(node, [ValuesKind.Expression]));
///
bool checkStackBase(TreeNode? node, int base) {
return checkStackBaseStateForAssert(helper.uri, node?.fileOffset, base);
}
/// Checks the top of the current stack against [kinds]. If a mismatch is
/// found, a top of the current stack is print along with the expected [kinds]
/// marking the frames that don't match, and throws an exception.
///
/// [base] it is used as the reference stack base height at which the [kinds]
/// are expected to occur, which allows for checking that the stack is empty
/// wrt. the stack base height.
///
/// Use this in assert statements like
///
/// assert(checkState(node,
/// [ValueKind.Expression, ValueKind.StatementOrNull],
/// base: stackBase));
///
/// to document the expected stack and get earlier errors on unexpected stack
/// content.
bool checkStack(TreeNode? node, int? base, List<ValueKind> kinds) {
return checkStackStateForAssert(helper.uri, node?.fileOffset, kinds,
base: base);
}
ClosureContext get closureContext => _closureContext!;
@override
StatementInferenceResult inferStatement(Statement statement,
[ClosureContext? closureContext]) {
ClosureContext? oldClosureContext = _closureContext;
if (closureContext != null) {
_closureContext = closureContext;
}
registerIfUnreachableForTesting(statement);
// For full (non-top level) inference, we need access to the
// ExpressionGeneratorHelper so that we can perform error recovery.
StatementInferenceResult result;
if (statement is InternalStatement) {
result = statement.acceptInference(this);
} else {
result = statement.accept(this);
}
_closureContext = oldClosureContext;
return result;
}
ExpressionInferenceResult _inferExpression(
Expression expression, DartType typeContext,
{bool isVoidAllowed = false, bool forEffect = false}) {
registerIfUnreachableForTesting(expression);
ExpressionInferenceResult result;
if (expression is ExpressionJudgment) {
result = expression.acceptInference(this, typeContext);
} else if (expression is InternalExpression) {
result = expression.acceptInference(this, typeContext);
} else {
result = expression.accept1(this, typeContext);
}
DartType inferredType = result.inferredType;
if (inferredType is VoidType && !isVoidAllowed) {
if (expression.parent is! ArgumentsImpl) {
helper.addProblem(
messageVoidExpression, expression.fileOffset, noLength);
}
}
if (coreTypes.isBottom(result.inferredType)) {
flowAnalysis.handleExit();
if (shouldThrowUnsoundnessException &&
// Don't throw on expressions that inherently return the bottom type.
!(result.nullAwareAction is Throw ||
result.nullAwareAction is Rethrow ||
result.nullAwareAction is InvalidExpression)) {
Expression replacement = createLet(
createVariable(result.expression, result.inferredType),
createReachabilityError(
expression.fileOffset, messageNeverValueError));
flowAnalysis.forwardExpression(replacement, result.expression);
result =
new ExpressionInferenceResult(result.inferredType, replacement);
}
}
return result;
}
@override
ExpressionInferenceResult inferExpression(
Expression expression, DartType typeContext,
{bool isVoidAllowed = false, bool forEffect = false}) {
ExpressionInferenceResult result = _inferExpression(expression, typeContext,
isVoidAllowed: isVoidAllowed, forEffect: forEffect);
return result.stopShorting();
}
@override
InitializerInferenceResult inferInitializer(Initializer initializer) {
InitializerInferenceResult inferenceResult;
if (initializer is InitializerJudgment) {
inferenceResult = initializer.acceptInference(this);
} else {
inferenceResult = initializer.accept(this);
}
return inferenceResult;
}
ExpressionInferenceResult inferNullAwareExpression(
Expression expression, DartType typeContext,
{bool isVoidAllowed = false, bool forEffect = false}) {
ExpressionInferenceResult result = _inferExpression(expression, typeContext,
isVoidAllowed: isVoidAllowed, forEffect: forEffect);
if (isNonNullableByDefault) {
return result;
} else {
return result.stopShorting();
}
}
void inferSyntheticVariable(VariableDeclarationImpl variable) {
assert(variable.isImplicitlyTyped);
assert(variable.initializer != null);
ExpressionInferenceResult result = inferExpression(
variable.initializer!, const UnknownType(),
isVoidAllowed: true);
variable.initializer = result.expression..parent = variable;
DartType inferredType =
inferDeclarationType(result.inferredType, forSyntheticVariable: true);
instrumentation?.record(uriForInstrumentation, variable.fileOffset, 'type',
new InstrumentationValueForType(inferredType));
variable.type = inferredType;
}
Link<NullAwareGuard> inferSyntheticVariableNullAware(
VariableDeclarationImpl variable) {
assert(variable.isImplicitlyTyped);
assert(variable.initializer != null);
ExpressionInferenceResult result = inferNullAwareExpression(
variable.initializer!, const UnknownType(),
isVoidAllowed: true);
Link<NullAwareGuard> nullAwareGuards = result.nullAwareGuards;
variable.initializer = result.nullAwareAction..parent = variable;
DartType inferredType =
inferDeclarationType(result.inferredType, forSyntheticVariable: true);
instrumentation?.record(uriForInstrumentation, variable.fileOffset, 'type',
new InstrumentationValueForType(inferredType));
variable.type = inferredType;
return nullAwareGuards;
}
/// Computes uri and offset for [node] for internal errors in a way that is
/// safe for both top-level and full inference.
UriOffset _computeUriOffset(TreeNode node) {
Uri uri = helper.uri;
int fileOffset = node.fileOffset;
return new UriOffset(uri, fileOffset);
}
ExpressionInferenceResult _unhandledExpression(
Expression node, DartType typeContext) {
UriOffset uriOffset = _computeUriOffset(node);
problems.unhandled("$node (${node.runtimeType})", "InferenceVisitor",
uriOffset.fileOffset, uriOffset.uri);
}
@override
ExpressionInferenceResult visitBlockExpression(
BlockExpression node, DartType typeContext) {
// This is only used for error cases. The spec doesn't use this and
// therefore doesn't specify the type context for the subterms.
StatementInferenceResult bodyResult = inferStatement(node.body);
if (bodyResult.hasChanged) {
node.body = (bodyResult.statement as Block)..parent = node;
}
ExpressionInferenceResult valueResult =
inferExpression(node.value, const UnknownType(), isVoidAllowed: true);
node.value = valueResult.expression..parent = node;
return new ExpressionInferenceResult(valueResult.inferredType, node);
}
@override
ExpressionInferenceResult visitConstantExpression(
ConstantExpression node, DartType typeContext) {
return _unhandledExpression(node, typeContext);
}
@override
ExpressionInferenceResult visitDynamicGet(
DynamicGet node, DartType typeContext) {
// The node has already been inferred, for instance as part of a for-in
// loop, so just compute the result type.
DartType resultType;
switch (node.kind) {
case DynamicAccessKind.Dynamic:
resultType = const DynamicType();
break;
case DynamicAccessKind.Never:
resultType = NeverType.fromNullability(libraryBuilder.nonNullable);
break;
case DynamicAccessKind.Invalid:
case DynamicAccessKind.Unresolved:
resultType = const InvalidType();
break;
}
return new ExpressionInferenceResult(resultType, node);
}
@override
ExpressionInferenceResult visitInstanceGet(
InstanceGet node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitInstanceTearOff(
InstanceTearOff node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitDynamicInvocation(
DynamicInvocation node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitDynamicSet(
DynamicSet node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitEqualsCall(
EqualsCall node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitEqualsNull(
EqualsNull node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitFunctionInvocation(
FunctionInvocation node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitInstanceInvocation(
InstanceInvocation node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitInstanceGetterInvocation(
InstanceGetterInvocation node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitInstanceSet(
InstanceSet node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitLocalFunctionInvocation(
LocalFunctionInvocation node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitStaticTearOff(
StaticTearOff node, DartType typeContext) {
ensureMemberType(node.target);
DartType type =
node.target.function.computeFunctionType(libraryBuilder.nonNullable);
return instantiateTearOff(type, typeContext, node);
}
@override
ExpressionInferenceResult visitFunctionTearOff(
FunctionTearOff node, DartType typeContext) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.getStaticType(staticTypeContext), node);
}
@override
ExpressionInferenceResult visitFileUriExpression(
FileUriExpression node, DartType typeContext) {
ExpressionInferenceResult result =
inferExpression(node.expression, typeContext);
node.expression = result.expression..parent = node;
return new ExpressionInferenceResult(result.inferredType, node);
}
@override
ExpressionInferenceResult visitInstanceCreation(
InstanceCreation node, DartType typeContext) {
return _unhandledExpression(node, typeContext);
}
@override
ExpressionInferenceResult visitConstructorTearOff(
ConstructorTearOff node, DartType typeContext) {
ensureMemberType(node.target);
DartType type =
node.target.function!.computeFunctionType(libraryBuilder.nonNullable);
return instantiateTearOff(type, typeContext, node);
}
@override
ExpressionInferenceResult visitRedirectingFactoryTearOff(
RedirectingFactoryTearOff node, DartType typeContext) {
DartType type =
node.target.function.computeFunctionType(libraryBuilder.nonNullable);
return instantiateTearOff(type, typeContext, node);
}
@override
ExpressionInferenceResult visitTypedefTearOff(
TypedefTearOff node, DartType typeContext) {
ExpressionInferenceResult expressionResult = inferExpression(
node.expression, const UnknownType(),
isVoidAllowed: true);
node.expression = expressionResult.expression..parent = node;
assert(
expressionResult.inferredType is FunctionType,
"Expected a FunctionType from tearing off a constructor from "
"a typedef, but got '${expressionResult.inferredType.runtimeType}'.");
FunctionType expressionType = expressionResult.inferredType as FunctionType;
assert(expressionType.typeParameters.length == node.typeArguments.length);
FunctionType resultType = FunctionTypeInstantiator.instantiate(
expressionType, node.typeArguments);
FreshStructuralParameters freshStructuralParameters =
getFreshStructuralParameters(node.structuralParameters);
resultType =
freshStructuralParameters.substitute(resultType) as FunctionType;
resultType = new FunctionType(resultType.positionalParameters,
resultType.returnType, resultType.declaredNullability,
namedParameters: resultType.namedParameters,
typeParameters: freshStructuralParameters.freshTypeParameters,
requiredParameterCount: resultType.requiredParameterCount);
ExpressionInferenceResult inferredResult =
instantiateTearOff(resultType, typeContext, node);
return ensureAssignableResult(typeContext, inferredResult);
}
@override
ExpressionInferenceResult visitListConcatenation(
ListConcatenation node, DartType typeContext) {
return _unhandledExpression(node, typeContext);
}
@override
ExpressionInferenceResult visitMapConcatenation(
MapConcatenation node, DartType typeContext) {
return _unhandledExpression(node, typeContext);
}
@override
ExpressionInferenceResult visitSetConcatenation(
SetConcatenation node, DartType typeContext) {
return _unhandledExpression(node, typeContext);
}
StatementInferenceResult _unhandledStatement(Statement node) {
UriOffset uriOffset = _computeUriOffset(node);
return problems.unhandled("${node.runtimeType}", "InferenceVisitor",
uriOffset.fileOffset, uriOffset.uri);
}
@override
StatementInferenceResult visitAssertBlock(AssertBlock node) {
return _unhandledStatement(node);
}
@override
StatementInferenceResult visitTryCatch(TryCatch node) {
return _unhandledStatement(node);
}
@override
StatementInferenceResult visitTryFinally(TryFinally node) {
return _unhandledStatement(node);
}
Never _unhandledInitializer(Initializer node) {
problems.unhandled("${node.runtimeType}", "InferenceVisitor",
node.fileOffset, node.location!.file);
}
@override
InitializerInferenceResult visitInvalidInitializer(InvalidInitializer node) {
_unhandledInitializer(node);
}
@override
InitializerInferenceResult visitLocalInitializer(LocalInitializer node) {
_unhandledInitializer(node);
}
@override
ExpressionInferenceResult visitInvalidExpression(
InvalidExpression node, DartType typeContext) {
if (node.expression != null) {
ExpressionInferenceResult result =
inferExpression(node.expression!, typeContext, isVoidAllowed: true);
node.expression = result.expression..parent = node;
}
return new ExpressionInferenceResult(const InvalidType(), node);
}
@override
ExpressionInferenceResult visitInstantiation(
Instantiation node, DartType typeContext) {
ExpressionInferenceResult operandResult = inferExpression(
node.expression, const UnknownType(),
isVoidAllowed: true);
Expression operand = operandResult.expression;
DartType operandType = operandResult.inferredType;
if (operandType is! FunctionType) {
ObjectAccessTarget callMember = findInterfaceMember(
operandType, callName, operand.fileOffset,
isSetter: false, includeExtensionMethods: true);
switch (callMember.kind) {
case ObjectAccessTargetKind.instanceMember:
Member? target = callMember.classMember;
if (target is Procedure && target.kind == ProcedureKind.Method) {
operandType = callMember.getGetterType(this);
operand = new InstanceTearOff(
InstanceAccessKind.Instance, operand, callName,
interfaceTarget: target, resultType: operandType)
..fileOffset = operand.fileOffset;
}
break;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.extensionTypeMember:
if (callMember.tearoffTarget != null &&
callMember.declarationMethodKind == ClassMemberKind.Method) {
operandType = callMember.getGetterType(this);
operand = new StaticInvocation(
callMember.tearoffTarget as Procedure,
new Arguments(<Expression>[operand],
types: callMember.receiverTypeArguments)
..fileOffset = operand.fileOffset)
..fileOffset = operand.fileOffset;
}
break;
case ObjectAccessTargetKind.nullableInstanceMember:
case ObjectAccessTargetKind.superMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.nullableExtensionMember:
case ObjectAccessTargetKind.dynamic:
case ObjectAccessTargetKind.never:
case ObjectAccessTargetKind.invalid:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.recordIndexed:
case ObjectAccessTargetKind.nullableRecordIndexed:
case ObjectAccessTargetKind.nullableRecordNamed:
case ObjectAccessTargetKind.recordNamed:
case ObjectAccessTargetKind.nullableExtensionTypeMember:
case ObjectAccessTargetKind.extensionTypeRepresentation:
case ObjectAccessTargetKind.nullableExtensionTypeRepresentation:
break;
}
}
node.expression = operand..parent = node;
Expression result = node;
DartType resultType = const InvalidType();
if (operandType is FunctionType) {
if (operandType.typeParameters.length == node.typeArguments.length) {
checkBoundsInInstantiation(
operandType, node.typeArguments, node.fileOffset,
inferred: false);
if (operandType.isPotentiallyNullable) {
result = helper.buildProblem(
templateInstantiationNullableGenericFunctionType.withArguments(
operandType, isNonNullableByDefault),
node.fileOffset,
noLength);
} else {
resultType = FunctionTypeInstantiator.instantiate(
operandType, node.typeArguments);
}
} else {
if (operandType.typeParameters.isEmpty) {
result = helper.buildProblem(
templateInstantiationNonGenericFunctionType.withArguments(
operandType, isNonNullableByDefault),
node.fileOffset,
noLength);
} else if (operandType.typeParameters.length >
node.typeArguments.length) {
result = helper.buildProblem(
templateInstantiationTooFewArguments.withArguments(
operandType.typeParameters.length, node.typeArguments.length),
node.fileOffset,
noLength);
} else if (operandType.typeParameters.length <
node.typeArguments.length) {
result = helper.buildProblem(
templateInstantiationTooManyArguments.withArguments(
operandType.typeParameters.length, node.typeArguments.length),
node.fileOffset,
noLength);
}
}
} else if (operandType is! InvalidType) {
result = helper.buildProblem(
templateInstantiationNonGenericFunctionType.withArguments(
operandType, isNonNullableByDefault),
node.fileOffset,
noLength);
}
return new ExpressionInferenceResult(resultType, result);
}
@override
ExpressionInferenceResult visitIntLiteral(
IntLiteral node, DartType typeContext) {
return new ExpressionInferenceResult(
coreTypes.intRawType(libraryBuilder.nonNullable), node);
}
@override
ExpressionInferenceResult visitAsExpression(
AsExpression node, DartType typeContext) {
ExpressionInferenceResult operandResult =
inferExpression(node.operand, const UnknownType(), isVoidAllowed: true);
node.operand = operandResult.expression..parent = node;
flowAnalysis.asExpression_end(node.operand, node.type);
return new ExpressionInferenceResult(node.type, node);
}
@override
InitializerInferenceResult visitAssertInitializer(AssertInitializer node) {
StatementInferenceResult result = inferStatement(node.statement);
if (result.hasChanged) {
node.statement = (result.statement as AssertStatement)..parent = node;
}
return const SuccessfulInitializerInferenceResult();
}
@override
StatementInferenceResult visitAssertStatement(AssertStatement node) {
flowAnalysis.assert_begin();
InterfaceType expectedType =
coreTypes.boolRawType(libraryBuilder.nonNullable);
ExpressionInferenceResult conditionResult =
inferExpression(node.condition, expectedType, isVoidAllowed: true);
Expression condition =
ensureAssignableResult(expectedType, conditionResult).expression;
node.condition = condition..parent = node;
flowAnalysis.assert_afterCondition(node.condition);
if (node.message != null) {
ExpressionInferenceResult messageResult = inferExpression(
node.message!, const UnknownType(),
isVoidAllowed: true);
node.message = messageResult.expression..parent = node;
}
flowAnalysis.assert_end();
return const StatementInferenceResult();
}
bool _isIncompatibleWithAwait(DartType type) {
if (isNullableTypeConstructorApplication(type)) {
return _isIncompatibleWithAwait(computeTypeWithoutNullabilityMarker(
(type),
isNonNullableByDefault: isNonNullableByDefault));
} else {
switch (type) {
case ExtensionType():
return typeSchemaEnvironment.hierarchy
.getExtensionTypeAsInstanceOfClass(
type, coreTypes.futureClass,
isNonNullableByDefault:
libraryBuilder.isNonNullableByDefault) ==
null;
case TypeParameterType():
return _isIncompatibleWithAwait(type.parameter.bound);
case StructuralParameterType():
return _isIncompatibleWithAwait(type.parameter.bound);
case IntersectionType():
return _isIncompatibleWithAwait(type.right);
case DynamicType():
case VoidType():
case FutureOrType():
case InterfaceType():
case TypedefType():
case FunctionType():
case RecordType():
case NullType():
case NeverType():
case AuxiliaryType():
case InvalidType():
return false;
}
}
}
@override
ExpressionInferenceResult visitAwaitExpression(
AwaitExpression node, DartType typeContext) {
if (typeContext is DynamicType) {
typeContext = const UnknownType();
}
typeContext = wrapFutureOrType(typeContext);
ExpressionInferenceResult operandResult = inferExpression(
node.operand, typeContext,
isVoidAllowed: !isNonNullableByDefault);
DartType operandType = operandResult.inferredType;
DartType flattenType = typeSchemaEnvironment.flatten(operandType);
if (_isIncompatibleWithAwait(operandType)) {
Expression wrapped = operandResult.expression;
node.operand = helper.wrapInProblem(
wrapped, messageAwaitOfExtensionTypeNotFuture, wrapped.fileOffset, 1);
wrapped.parent = node.operand;
} else {
node.operand = operandResult.expression..parent = node;
}
DartType runtimeCheckType = new InterfaceType(
coreTypes.futureClass, libraryBuilder.nonNullable, [flattenType]);
if (!typeSchemaEnvironment.isSubtypeOf(
operandType, runtimeCheckType, SubtypeCheckMode.withNullabilities)) {
node.runtimeCheckType = runtimeCheckType;
}
return new ExpressionInferenceResult(flattenType, node);
}
List<Statement>? _visitStatements<T extends Statement>(List<T> statements) {
List<Statement>? result;
for (int index = 0; index < statements.length; index++) {
T statement = statements[index];
StatementInferenceResult statementResult = inferStatement(statement);
if (statementResult.hasChanged) {
if (result == null) {
result = <T>[];
result.addAll(statements.sublist(0, index));
}
if (statementResult.statementCount == 1) {
result.add(statementResult.statement);
} else {
result.addAll(statementResult.statements);
}
} else if (result != null) {
result.add(statement);
}
}
return result;
}
@override
StatementInferenceResult visitBlock(Block node) {
registerIfUnreachableForTesting(node);
List<Statement>? result = _visitStatements<Statement>(node.statements);
if (result != null) {
Block block = new Block(result)..fileOffset = node.fileOffset;
libraryBuilder.loader.dataForTesting?.registerAlias(node, block);
return new StatementInferenceResult.single(block);
} else {
return const StatementInferenceResult();
}
}
@override
ExpressionInferenceResult visitBoolLiteral(
BoolLiteral node, DartType typeContext) {
flowAnalysis.booleanLiteral(node, node.value);
return new ExpressionInferenceResult(
coreTypes.boolRawType(libraryBuilder.nonNullable), node);
}
@override
StatementInferenceResult visitBreakStatement(
covariant BreakStatementImpl node) {
// TODO(johnniwinther): Refactor break/continue encoding.
assert(node.targetStatement != null);
if (node.isContinue) {
flowAnalysis.handleContinue(node.targetStatement);
} else {
flowAnalysis.handleBreak(node.targetStatement);
}
return const StatementInferenceResult();
}
ExpressionInferenceResult visitCascade(Cascade node, DartType typeContext) {
ExpressionInferenceResult result = inferExpression(
node.variable.initializer!, typeContext,
isVoidAllowed: false);
node.variable.initializer = result.expression..parent = node.variable;
node.variable.type = result.inferredType;
flowAnalysis.cascadeExpression_afterTarget(
result.expression, result.inferredType,
isNullAware: node.isNullAware);
NullAwareGuard? nullAwareGuard;
if (node.isNullAware) {
nullAwareGuard = createNullAwareGuard(node.variable);
}
Cascade? previousEnclosingCascade = _enclosingCascade;
_enclosingCascade = node;
List<ExpressionInferenceResult> expressionResults =
<ExpressionInferenceResult>[];
for (Expression expression in node.expressions) {
expressionResults.add(inferExpression(expression, const UnknownType(),
isVoidAllowed: true, forEffect: true));
}
List<Statement> body = [];
for (int index = 0; index < expressionResults.length; index++) {
body.add(_createExpressionStatement(expressionResults[index].expression));
}
_enclosingCascade = previousEnclosingCascade;
Expression replacement = _createBlockExpression(node.variable.fileOffset,
_createBlock(body), createVariableGet(node.variable));
if (node.isNullAware) {
replacement =
nullAwareGuard!.createExpression(result.inferredType, replacement);
} else {
replacement = new Let(node.variable, replacement)
..fileOffset = node.fileOffset;
}
flowAnalysis.cascadeExpression_end(replacement);
return new ExpressionInferenceResult(result.inferredType, replacement);
}
@override
PropertyTarget<Expression> computePropertyTarget(Expression target) {
if (_enclosingCascade case Cascade(:var variable)
when target is VariableGet && target.variable == variable) {
// `target` is an implicit reference to the target of a cascade
// expression; flow analysis uses `CascadePropertyTarget` to represent
// this situation.
return CascadePropertyTarget.singleton;
} else {
// `target` is an ordinary expression.
return new ExpressionPropertyTarget(target);
}
}
Block _createBlock(List<Statement> statements) {
return new Block(statements);
}
BlockExpression _createBlockExpression(
int fileOffset, Block body, Expression value) {
assert(fileOffset != TreeNode.noOffset);
return new BlockExpression(body, value)..fileOffset = fileOffset;
}
ExpressionStatement _createExpressionStatement(Expression expression) {
assert(expression.fileOffset != TreeNode.noOffset);
return new ExpressionStatement(expression)
..fileOffset = expression.fileOffset;
}
@override
ExpressionInferenceResult visitConditionalExpression(
ConditionalExpression node, DartType typeContext) {
flowAnalysis.conditional_conditionBegin();
InterfaceType expectedType =
coreTypes.boolRawType(libraryBuilder.nonNullable);
ExpressionInferenceResult conditionResult =
inferExpression(node.condition, expectedType, isVoidAllowed: true);
Expression condition =
ensureAssignableResult(expectedType, conditionResult).expression;
node.condition = condition..parent = node;
flowAnalysis.conditional_thenBegin(node.condition, node);
bool isThenReachable = flowAnalysis.isReachable;
// A conditional expression `E` of the form `b ? e1 : e2` with context
// type `K` is analyzed as follows:
//
// - Let `T1` be the type of `e1` inferred with context type `K`
ExpressionInferenceResult thenResult =
inferExpression(node.then, typeContext, isVoidAllowed: true);
node.then = thenResult.expression..parent = node;
registerIfUnreachableForTesting(node.then, isReachable: isThenReachable);
DartType t1 = thenResult.inferredType;
// - Let `T2` be the type of `e2` inferred with context type `K`
flowAnalysis.conditional_elseBegin(node.then, thenResult.inferredType);
bool isOtherwiseReachable = flowAnalysis.isReachable;
ExpressionInferenceResult otherwiseResult =
inferExpression(node.otherwise, typeContext, isVoidAllowed: true);
node.otherwise = otherwiseResult.expression..parent = node;
registerIfUnreachableForTesting(node.otherwise,
isReachable: isOtherwiseReachable);
DartType t2 = otherwiseResult.inferredType;
// - Let `T` be `UP(T1, T2)`
DartType t = typeSchemaEnvironment.getStandardUpperBound(t1, t2,
isNonNullableByDefault: isNonNullableByDefault);
// - Let `S` be the greatest closure of `K`
DartType s = computeGreatestClosure(typeContext);
DartType inferredType;
// If `inferenceUpdate3` is not enabled, then the type of `E` is `T`.
if (!libraryBuilder.libraryFeatures.inferenceUpdate3.isEnabled) {
inferredType = t;
} else
// - If `T <: S` then the type of `E` is `T`
if (typeSchemaEnvironment.isSubtypeOf(
t, s, SubtypeCheckMode.withNullabilities)) {
inferredType = t;
} else
// - Otherwise, if `T1 <: S` and `T2 <: S`, then the type of `E` is `S`
if (typeSchemaEnvironment.isSubtypeOf(
t1, s, SubtypeCheckMode.withNullabilities) &&
typeSchemaEnvironment.isSubtypeOf(
t2, s, SubtypeCheckMode.withNullabilities)) {
inferredType = s;
} else
// - Otherwise, the type of `E` is `T`
{
inferredType = t;
}
flowAnalysis.conditional_end(
node, inferredType, node.otherwise, otherwiseResult.inferredType);
node.staticType = inferredType;
return new ExpressionInferenceResult(inferredType, node);
}
@override
ExpressionInferenceResult visitConstructorInvocation(
ConstructorInvocation node, DartType typeContext) {
ensureMemberType(node.target);
bool hadExplicitTypeArguments = hasExplicitTypeArguments(node.arguments);
FunctionType functionType = node.target.function
.computeThisFunctionType(libraryBuilder.nonNullable);
InvocationInferenceResult result = inferInvocation(this, typeContext,
node.fileOffset, functionType, node.arguments as ArgumentsImpl,
isConst: node.isConst, staticTarget: node.target);
SourceLibraryBuilder library = libraryBuilder;
if (!hadExplicitTypeArguments) {
library.checkBoundsInConstructorInvocation(
node, typeSchemaEnvironment, helper.uri,
inferred: true);
}
return new ExpressionInferenceResult(
result.inferredType, result.applyResult(node));
}
@override
StatementInferenceResult visitContinueSwitchStatement(
ContinueSwitchStatement node) {
flowAnalysis.handleContinue(node.target.body);
return const StatementInferenceResult();
}
ExpressionInferenceResult visitExtensionTearOff(
ExtensionTearOff node, DartType typeContext) {
FunctionType calleeType =
node.target.function.computeFunctionType(libraryBuilder.nonNullable);
TypeArgumentsInfo typeArgumentsInfo = getTypeArgumentsInfo(node.arguments);
InvocationInferenceResult result = inferInvocation(this, typeContext,
node.fileOffset, calleeType, node.arguments as ArgumentsImpl,
staticTarget: node.target);
StaticInvocation replacement =
new StaticInvocation(node.target, node.arguments);
libraryBuilder.checkBoundsInStaticInvocation(
replacement, typeSchemaEnvironment, helper.uri, typeArgumentsInfo);
return instantiateTearOff(
result.inferredType, typeContext, result.applyResult(replacement));
}
ExpressionInferenceResult visitExtensionSet(
ExtensionSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferExpression(
node.receiver, const UnknownType(),
isVoidAllowed: false);
List<DartType> extensionTypeArguments = computeExtensionTypeArgument(
node.extension, node.explicitTypeArguments, receiverResult.inferredType,
treeNodeForTesting: node);
DartType receiverType =
getExtensionReceiverType(node.extension, extensionTypeArguments);
Expression receiver =
ensureAssignableResult(receiverType, receiverResult).expression;
ObjectAccessTarget target = new ExtensionAccessTarget(receiverType,
node.target, null, ClassMemberKind.Setter, extensionTypeArguments);
DartType valueType = target.getSetterType(this);
ExpressionInferenceResult valueResult =
inferExpression(node.value, valueType, isVoidAllowed: false);
valueResult = ensureAssignableResult(valueType, valueResult);
Expression value = valueResult.expression;
VariableDeclaration? valueVariable;
if (node.forEffect) {
// No need for value variable.
} else {
valueVariable = createVariable(value, valueResult.inferredType);
value = createVariableGet(valueVariable);
}
VariableDeclaration? receiverVariable;
if (node.forEffect || isPureExpression(receiver)) {
// No need for receiver variable.
} else {
receiverVariable = createVariable(receiver, receiverResult.inferredType);
receiver = createVariableGet(receiverVariable);
}
Expression assignment = new StaticInvocation(
node.target,
new Arguments(<Expression>[receiver, value],
types: extensionTypeArguments)
..fileOffset = node.fileOffset)
..fileOffset = node.fileOffset;
Expression replacement;
if (node.forEffect) {
assert(receiverVariable == null);
assert(valueVariable == null);
replacement = assignment;
} else {
assert(valueVariable != null);
VariableDeclaration assignmentVariable =
createVariable(assignment, const VoidType());
replacement = createLet(valueVariable!,
createLet(assignmentVariable, createVariableGet(valueVariable)));
if (receiverVariable != null) {
replacement = createLet(receiverVariable, replacement);
}
}
replacement.fileOffset = node.fileOffset;
return new ExpressionInferenceResult(valueResult.inferredType, replacement);
}
ExpressionInferenceResult visitCompoundExtensionSet(
CompoundExtensionSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferExpression(
node.receiver, const UnknownType(),
isVoidAllowed: false);
List<DartType> extensionTypeArguments = computeExtensionTypeArgument(
node.extension, node.explicitTypeArguments, receiverResult.inferredType,
treeNodeForTesting: node);
DartType receiverType =
getExtensionReceiverType(node.extension, extensionTypeArguments);
Expression receiver =
ensureAssignableResult(receiverType, receiverResult).expression;
VariableDeclaration? receiverVariable;
Expression readReceiver;
Expression writeReceiver;
if (isPureExpression(receiver)) {
readReceiver = receiver;
writeReceiver = clonePureExpression(receiver);
} else {
receiverVariable = createVariable(receiver, receiverType);
readReceiver = createVariableGet(receiverVariable);
writeReceiver = createVariableGet(receiverVariable);
}
ObjectAccessTarget readTarget = node.getter == null
? const ObjectAccessTarget.missing()
: new ExtensionAccessTarget(receiverType, node.getter!, null,
ClassMemberKind.Getter, extensionTypeArguments);
DartType readType = readTarget.getGetterType(this);
Expression read;
if (readTarget.isMissing) {
read = createMissingPropertyGet(
node.readOffset, readType, node.propertyName,
receiver: readReceiver);
} else {
assert(readTarget.isExtensionMember);
read = new StaticInvocation(
readTarget.member as Procedure,
new Arguments(<Expression>[
readReceiver,
], types: readTarget.receiverTypeArguments)
..fileOffset = node.readOffset)
..fileOffset = node.readOffset;
}
ObjectAccessTarget writeTarget = node.setter == null
? const ObjectAccessTarget.missing()
: new ExtensionAccessTarget(receiverType, node.setter!, null,
ClassMemberKind.Setter, extensionTypeArguments);
DartType valueType = writeTarget.getSetterType(this);
ExpressionInferenceResult binaryResult = _computeBinaryExpression(
node.binaryOffset,
valueType,
read,
readType,
node.binaryName,
node.rhs,
null);
binaryResult =
ensureAssignableResult(valueType, binaryResult, isVoidAllowed: true);
Expression value = binaryResult.expression;
VariableDeclaration? valueVariable;
if (node.forEffect) {
// No need for value variable.
} else {
valueVariable = createVariable(value, valueType);
value = createVariableGet(valueVariable);
}
Expression write;
if (writeTarget.isMissing) {
write = createMissingPropertySet(
node.writeOffset, writeReceiver, readType, node.propertyName, value,
forEffect: node.forEffect);
} else {
assert(writeTarget.isExtensionMember);
write = new StaticInvocation(
writeTarget.member as Procedure,
new Arguments(<Expression>[
writeReceiver,
value,
], types: writeTarget.receiverTypeArguments)
..fileOffset = node.writeOffset)
..fileOffset = node.writeOffset;
}
Expression replacement;
if (node.forEffect) {
assert(valueVariable == null);
replacement = write;
} else {
assert(valueVariable != null);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
replacement = createLet(valueVariable!,
createLet(writeVariable, createVariableGet(valueVariable)));
}
if (receiverVariable != null) {
replacement = createLet(receiverVariable, replacement);
}
replacement.fileOffset = node.fileOffset;
return new ExpressionInferenceResult(valueType, replacement);
}
ExpressionInferenceResult visitDeferredCheck(
DeferredCheck node, DartType typeContext) {
// Since the variable is not used in the body we don't need to type infer
// it. We can just type infer the body.
ExpressionInferenceResult result =
inferExpression(node.expression, typeContext, isVoidAllowed: true);
Expression replacement = new Let(node.variable, result.expression)
..fileOffset = node.fileOffset;
return new ExpressionInferenceResult(result.inferredType, replacement);
}
@override
StatementInferenceResult visitDoStatement(DoStatement node) {
flowAnalysis.doStatement_bodyBegin(node);
StatementInferenceResult bodyResult = inferStatement(node.body);
if (bodyResult.hasChanged) {
node.body = bodyResult.statement..parent = node;
}
flowAnalysis.doStatement_conditionBegin();
InterfaceType boolType = coreTypes.boolRawType(libraryBuilder.nonNullable);
ExpressionInferenceResult conditionResult =
inferExpression(node.condition, boolType, isVoidAllowed: true);
Expression condition =
ensureAssignableResult(boolType, conditionResult).expression;
node.condition = condition..parent = node;
flowAnalysis.doStatement_end(condition);
return const StatementInferenceResult();
}
@override
ExpressionInferenceResult visitDoubleLiteral(
DoubleLiteral node, DartType typeContext) {
return new ExpressionInferenceResult(
coreTypes.doubleRawType(libraryBuilder.nonNullable), node);
}
@override
StatementInferenceResult visitEmptyStatement(EmptyStatement node) {
// No inference needs to be done.
return const StatementInferenceResult();
}
@override
StatementInferenceResult visitExpressionStatement(ExpressionStatement node) {
ExpressionInferenceResult result = inferExpression(
node.expression, const UnknownType(),
isVoidAllowed: true, forEffect: true);
node.expression = result.expression..parent = node;
return const StatementInferenceResult();
}
ExpressionInferenceResult visitFactoryConstructorInvocation(
FactoryConstructorInvocation node, DartType typeContext) {
bool hadExplicitTypeArguments = hasExplicitTypeArguments(node.arguments);
FunctionType functionType = node.target.function
.computeThisFunctionType(libraryBuilder.nonNullable);
InvocationInferenceResult result = inferInvocation(this, typeContext,
node.fileOffset, functionType, node.arguments as ArgumentsImpl,
isConst: node.isConst, staticTarget: node.target);
node.hasBeenInferred = true;
Expression resultNode = node;
SourceLibraryBuilder library = libraryBuilder;
if (!hadExplicitTypeArguments) {
library.checkBoundsInFactoryInvocation(
node, typeSchemaEnvironment, helper.uri,
inferred: true);
}
return new ExpressionInferenceResult(
result.inferredType, result.applyResult(resultNode));
}
/// Returns the function type of [constructor] when called through [typedef].
FunctionType _computeAliasedConstructorFunctionType(
Constructor constructor, Typedef typedef) {
ensureMemberType(constructor);
FunctionNode function = constructor.function;
// We need create a copy of the list of type parameters, otherwise
// transformations like erasure don't work.
List<TypeParameter> classTypeParametersCopy =
new List.of(constructor.enclosingClass.typeParameters);
FreshStructuralParametersFromTypeParameters freshTypeParameters =
getFreshStructuralParametersFromTypeParameters(typedef.typeParameters);
List<StructuralParameter> typedefTypeParametersCopy =
freshTypeParameters.freshTypeParameters;
List<DartType> asTypeArguments = freshTypeParameters.freshTypeArguments;
TypedefType typedefType = new TypedefType(
typedef, libraryBuilder.library.nonNullable, asTypeArguments);
DartType unaliasedTypedef = typedefType.unalias;
assert(unaliasedTypedef is InterfaceType,
"[typedef] is assumed to resolve to an interface type");
InterfaceType targetType = unaliasedTypedef as InterfaceType;
Substitution substitution = Substitution.fromPairs(
classTypeParametersCopy, targetType.typeArguments);
List<DartType> positional = function.positionalParameters
.map((VariableDeclaration decl) =>
substitution.substituteType(decl.type))
.toList(growable: false);
List<NamedType> named = function.namedParameters
.map((VariableDeclaration decl) => new NamedType(
decl.name!, substitution.substituteType(decl.type),
isRequired: decl.isRequired))
.toList(growable: false);
named.sort();
return new FunctionType(
positional, typedefType.unalias, libraryBuilder.library.nonNullable,
namedParameters: named,
typeParameters: typedefTypeParametersCopy,
requiredParameterCount: function.requiredParameterCount);
}
ExpressionInferenceResult visitTypeAliasedConstructorInvocation(
TypeAliasedConstructorInvocation node, DartType typeContext) {
assert(getExplicitTypeArguments(node.arguments) == null);
Typedef typedef = node.typeAliasBuilder.typedef;
FunctionType calleeType =
_computeAliasedConstructorFunctionType(node.target, typedef);
calleeType = replaceReturnType(calleeType, calleeType.returnType.unalias);
InvocationInferenceResult result = inferInvocation(this, typeContext,
node.fileOffset, calleeType, node.arguments as ArgumentsImpl,
isConst: node.isConst, staticTarget: node.target);
node.hasBeenInferred = true;
Expression resultNode = node;
return new ExpressionInferenceResult(
result.inferredType, result.applyResult(resultNode));
}
/// Returns the function type of [factory] when called through [typedef].
FunctionType _computeAliasedFactoryFunctionType(
Procedure factory, Typedef typedef) {
assert(factory.isFactory || factory.isExtensionTypeMember,
"Only run this method on a factory: $factory");
ensureMemberType(factory);
FunctionNode function = factory.function;
// We need create a copy of the list of type parameters, otherwise
// transformations like erasure don't work.
List<TypeParameter> classTypeParametersCopy =
new List.of(function.typeParameters);
FreshStructuralParametersFromTypeParameters freshTypeParameters =
getFreshStructuralParametersFromTypeParameters(typedef.typeParameters);
List<StructuralParameter> typedefTypeParametersCopy =
freshTypeParameters.freshTypeParameters;
List<DartType> asTypeArguments = freshTypeParameters.freshTypeArguments;
TypedefType typedefType = new TypedefType(
typedef, libraryBuilder.library.nonNullable, asTypeArguments);
DartType unaliasedTypedef = typedefType.unalias;
assert(unaliasedTypedef is TypeDeclarationType,
"[typedef] is assumed to resolve to a type declaration type");
TypeDeclarationType targetType = unaliasedTypedef as TypeDeclarationType;
Substitution substitution = Substitution.fromPairs(
classTypeParametersCopy, targetType.typeArguments);
List<DartType> positional = function.positionalParameters
.map((VariableDeclaration decl) =>
substitution.substituteType(decl.type))
.toList(growable: false);
List<NamedType> named = function.namedParameters
.map((VariableDeclaration decl) => new NamedType(
decl.name!, substitution.substituteType(decl.type),
isRequired: decl.isRequired))
.toList(growable: false);
named.sort();
return new FunctionType(
positional, typedefType.unalias, libraryBuilder.library.nonNullable,
namedParameters: named,
typeParameters: typedefTypeParametersCopy,
requiredParameterCount: function.requiredParameterCount);
}
ExpressionInferenceResult visitTypeAliasedFactoryInvocation(
TypeAliasedFactoryInvocation node, DartType typeContext) {
assert(getExplicitTypeArguments(node.arguments) == null);
Typedef typedef = node.typeAliasBuilder.typedef;
FunctionType calleeType =
_computeAliasedFactoryFunctionType(node.target, typedef);
calleeType = replaceReturnType(calleeType, calleeType.returnType.unalias);
InvocationInferenceResult result = inferInvocation(this, typeContext,
node.fileOffset, calleeType, node.arguments as ArgumentsImpl,
isConst: node.isConst, staticTarget: node.target);
node.hasBeenInferred = true;
Expression resultNode = node;
return new ExpressionInferenceResult(
result.inferredType, result.applyResult(resultNode));
}
@override
InitializerInferenceResult visitFieldInitializer(FieldInitializer node) {
DartType fieldType = node.field.type;
fieldType = constructorDeclaration!.substituteFieldType(fieldType);
ExpressionInferenceResult initializerResult =
inferExpression(node.value, fieldType);
Expression initializer = ensureAssignableResult(
fieldType, initializerResult,
fileOffset: node.fileOffset)
.expression;
node.value = initializer..parent = node;
return const SuccessfulInitializerInferenceResult();
}
ForInResult handleForInDeclaringVariable(
TreeNode node,
VariableDeclaration variable,
Expression iterable,
Statement? expressionEffects,
{bool isAsync = false}) {
DartType elementType;
bool isVariableTypeNeeded = false;
if (variable is VariableDeclarationImpl && variable.isImplicitlyTyped) {
isVariableTypeNeeded = true;
elementType = const UnknownType();
} else {
elementType = variable.type;
}
ExpressionInferenceResult iterableResult =
inferForInIterable(iterable, elementType, isAsync: isAsync);
DartType inferredType = iterableResult.inferredType;
if (isVariableTypeNeeded) {
instrumentation?.record(uriForInstrumentation, variable.fileOffset,
'type', new InstrumentationValueForType(inferredType));
variable.type = inferredType;
}
// This is matched by the call to [forEach_end] in
// [inferElement], [inferMapEntry] or [inferForInStatement].
flowAnalysis.declare(variable, variable.type, initialized: true);
flowAnalysis.forEach_bodyBegin(node);
VariableDeclaration tempVariable = new VariableDeclaration(null,
type: inferredType, isFinal: true, isSynthesized: true);
VariableGet variableGet = new VariableGet(tempVariable)
..fileOffset = variable.fileOffset;
TreeNode parent = variable.parent!;
Expression implicitDowncast = ensureAssignable(
variable.type, inferredType, variableGet,
isVoidAllowed: true,
fileOffset: parent.fileOffset,
errorTemplate: templateForInLoopElementTypeNotAssignable,
nullabilityErrorTemplate:
templateForInLoopElementTypeNotAssignableNullability,
nullabilityPartErrorTemplate:
templateForInLoopElementTypeNotAssignablePartNullability);
Statement? expressionEffect;
if (!identical(implicitDowncast, variableGet)) {
variable.initializer = implicitDowncast..parent = variable;
expressionEffect = variable;
variable = tempVariable;
}
if (expressionEffects != null) {
StatementInferenceResult bodyResult = inferStatement(expressionEffects);
if (bodyResult.hasChanged) {
expressionEffects = bodyResult.statement;
}
if (expressionEffect != null) {
expressionEffects =
combineStatements(expressionEffect, expressionEffects);
}
} else {
expressionEffects = expressionEffect;
}
return new ForInResult(
variable, iterableResult.expression, null, expressionEffects);
}
ExpressionInferenceResult inferForInIterable(
Expression iterable, DartType elementType,
{bool isAsync = false}) {
Class iterableClass =
isAsync ? coreTypes.streamClass : coreTypes.iterableClass;
DartType context =
wrapType(elementType, iterableClass, libraryBuilder.nonNullable);
ExpressionInferenceResult iterableResult =
inferExpression(iterable, context, isVoidAllowed: false);
DartType iterableType = iterableResult.inferredType;
iterable = iterableResult.expression;
DartType inferredExpressionType = iterableType.nonTypeVariableBound;
iterable = ensureAssignable(
wrapType(
const DynamicType(), iterableClass, libraryBuilder.nonNullable),
inferredExpressionType,
iterable,
errorTemplate: templateForInLoopTypeNotIterable,
nullabilityErrorTemplate: templateForInLoopTypeNotIterableNullability,
nullabilityPartErrorTemplate:
templateForInLoopTypeNotIterablePartNullability);
DartType inferredType = const DynamicType();
if (inferredExpressionType is TypeDeclarationType) {
// TODO(johnniwinther): Should we use the type of
// `iterable.iterator.current` instead?
List<DartType>? supertypeArguments = hierarchyBuilder
.getTypeArgumentsAsInstanceOf(inferredExpressionType, iterableClass);
if (supertypeArguments != null) {
inferredType = supertypeArguments[0];
}
}
return new ExpressionInferenceResult(inferredType, iterable);
}
ForInVariable computeForInVariable(
Expression? syntheticAssignment, bool hasProblem) {
if (syntheticAssignment is VariableSet) {
return new LocalForInVariable(syntheticAssignment);
} else if (syntheticAssignment is PropertySet) {
return new PropertyForInVariable(syntheticAssignment);
} else if (syntheticAssignment is AbstractSuperPropertySet) {
return new AbstractSuperPropertyForInVariable(syntheticAssignment);
} else if (syntheticAssignment is SuperPropertySet) {
return new SuperPropertyForInVariable(syntheticAssignment);
} else if (syntheticAssignment is StaticSet) {
return new StaticForInVariable(syntheticAssignment);
} else if (syntheticAssignment is InvalidExpression || hasProblem) {
return new InvalidForInVariable(syntheticAssignment);
} else {
UriOffset uriOffset = _computeUriOffset(syntheticAssignment!);
return problems.unhandled(
"${syntheticAssignment.runtimeType}",
"handleForInStatementWithoutVariable",
uriOffset.fileOffset,
uriOffset.uri);
}
}
ForInResult _handleForInWithoutVariable(
TreeNode node,
VariableDeclaration variable,
Expression iterable,
Expression? syntheticAssignment,
Statement? expressionEffects,
{bool isAsync = false,
required bool hasProblem}) {
ForInVariable forInVariable =
computeForInVariable(syntheticAssignment, hasProblem);
DartType elementType = forInVariable.computeElementType(this);
ExpressionInferenceResult iterableResult =
inferForInIterable(iterable, elementType, isAsync: isAsync);
DartType inferredType = iterableResult.inferredType;
variable.type = inferredType;
// This is matched by the call to [forEach_end] in
// [inferElement], [inferMapEntry] or [inferForInStatement].
flowAnalysis.forEach_bodyBegin(node);
syntheticAssignment = forInVariable.inferAssignment(this, inferredType);
if (syntheticAssignment is VariableSet) {
flowAnalysis.write(node, variable, inferredType, null);
}
if (expressionEffects != null) {
StatementInferenceResult result = inferStatement(expressionEffects);
expressionEffects =
result.hasChanged ? result.statement : expressionEffects;
}
return new ForInResult(variable, iterableResult.expression,
syntheticAssignment, expressionEffects);
}
ForInResult _handlePatternForIn(
TreeNode node,
VariableDeclaration variable,
Expression iterable,
Expression? syntheticAssignment,
PatternVariableDeclaration patternVariableDeclaration,
{bool isAsync = false,
required bool hasProblem}) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
PatternForInResult<DartType, InvalidExpression> result =
analyzePatternForIn(
node: node,
hasAwait: isAsync,
pattern: patternVariableDeclaration.pattern,
expression: iterable,
dispatchBody: () {});
patternVariableDeclaration.matchedValueType = result.elementType;
if (result.patternForInExpressionIsNotIterableError != null) {
// The error is reported elsewhere.
}
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
/* initializer = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite();
if (!identical(rewrite, patternVariableDeclaration.pattern)) {
patternVariableDeclaration.pattern = (rewrite as Pattern)
..parent = patternVariableDeclaration;
}
rewrite = popRewrite();
if (!identical(rewrite, patternVariableDeclaration.initializer)) {
iterable = (rewrite as Expression)..parent = node;
}
ForInVariable forInVariable =
new PatternVariableDeclarationForInVariable(patternVariableDeclaration);
variable.type = result.elementType;
iterable = ensureAssignable(
wrapType(
const DynamicType(),
isAsync ? coreTypes.streamClass : coreTypes.iterableClass,
libraryBuilder.nonNullable),
result.expressionType,
iterable,
errorTemplate: templateForInLoopTypeNotIterable,
nullabilityErrorTemplate: templateForInLoopTypeNotIterableNullability,
nullabilityPartErrorTemplate:
templateForInLoopTypeNotIterablePartNullability);
// This is matched by the call to [forEach_end] in
// [inferElement], [inferMapEntry] or [inferForInStatement].
flowAnalysis.forEach_bodyBegin(node);
syntheticAssignment =
forInVariable.inferAssignment(this, result.elementType);
if (syntheticAssignment is VariableSet) {
flowAnalysis.write(node, variable, result.elementType, null);
}
return new ForInResult(variable, /*iterableResult.expression*/ iterable,
syntheticAssignment, patternVariableDeclaration);
}
ForInResult handleForInWithoutVariable(
TreeNode node,
VariableDeclaration variable,
Expression iterable,
Expression? syntheticAssignment,
Statement? expressionEffects,
{bool isAsync = false,
required bool hasProblem}) {
if (expressionEffects is PatternVariableDeclaration) {
return _handlePatternForIn(
node, variable, iterable, syntheticAssignment, expressionEffects,
isAsync: isAsync, hasProblem: hasProblem);
} else {
return _handleForInWithoutVariable(
node, variable, iterable, syntheticAssignment, expressionEffects,
isAsync: isAsync, hasProblem: hasProblem);
}
}
@override
StatementInferenceResult visitForInStatement(ForInStatement node) {
assert(node.variable.name != null);
ForInResult result = handleForInDeclaringVariable(
node, node.variable, node.iterable, null,
isAsync: node.isAsync);
StatementInferenceResult bodyResult = inferStatement(node.body);
// This is matched by the call to [forEach_bodyBegin] in
// [handleForInWithoutVariable] or [handleForInDeclaringVariable].
flowAnalysis.forEach_end();
Statement body = bodyResult.hasChanged ? bodyResult.statement : node.body;
if (result.expressionSideEffects != null) {
body = combineStatements(result.expressionSideEffects!, body);
}
if (result.syntheticAssignment != null) {
body = combineStatements(
createExpressionStatement(result.syntheticAssignment!), body);
}
node.variable = result.variable..parent = node;
node.iterable = result.iterable..parent = node;
node.body = body..parent = node;
return const StatementInferenceResult();
}
StatementInferenceResult visitForInStatementWithSynthesizedVariable(
ForInStatementWithSynthesizedVariable node) {
assert(node.variable!.name == null);
ForInResult result = handleForInWithoutVariable(node, node.variable!,
node.iterable, node.syntheticAssignment, node.expressionEffects,
isAsync: node.isAsync, hasProblem: node.hasProblem);
StatementInferenceResult bodyResult = inferStatement(node.body);
// This is matched by the call to [forEach_bodyBegin] in
// [handleForInWithoutVariable] or [handleForInDeclaringVariable].
flowAnalysis.forEach_end();
Statement body = bodyResult.hasChanged ? bodyResult.statement : node.body;
if (result.expressionSideEffects != null) {
body = combineStatements(result.expressionSideEffects!, body);
}
if (result.syntheticAssignment != null) {
body = combineStatements(
createExpressionStatement(result.syntheticAssignment!), body);
}
Statement replacement = new ForInStatement(
result.variable, result.iterable, body,
isAsync: node.isAsync)
..fileOffset = node.fileOffset
..bodyOffset = node.bodyOffset;
libraryBuilder.loader.dataForTesting?.registerAlias(node, replacement);
return new StatementInferenceResult.single(replacement);
}
@override
StatementInferenceResult visitForStatement(ForStatement node) {
List<VariableDeclaration>? variables;
for (int index = 0; index < node.variables.length; index++) {
VariableDeclaration variable = node.variables[index];
if (variable.name == null) {
if (variable.initializer != null) {
ExpressionInferenceResult result = inferExpression(
variable.initializer!, const UnknownType(),
isVoidAllowed: true);
variable.initializer = result.expression..parent = variable;
variable.type = result.inferredType;
}
} else {
StatementInferenceResult variableResult = inferStatement(variable);
if (variableResult.hasChanged) {
if (variables == null) {
variables = <VariableDeclaration>[];
variables.addAll(node.variables.sublist(0, index));
}
if (variableResult.statementCount == 1) {
variables.add(variableResult.statement as VariableDeclaration);
} else {
for (Statement variable in variableResult.statements) {
variables.add(variable as VariableDeclaration);
}
}
} else if (variables != null) {
variables.add(variable);
}
}
}
if (variables != null) {
node.variables.clear();
node.variables.addAll(variables);
setParents(variables, node);
}
flowAnalysis.for_conditionBegin(node);
if (node.condition != null) {
InterfaceType expectedType =
coreTypes.boolRawType(libraryBuilder.nonNullable);
ExpressionInferenceResult conditionResult =
inferExpression(node.condition!, expectedType, isVoidAllowed: true);
Expression condition =
ensureAssignableResult(expectedType, conditionResult).expression;
node.condition = condition..parent = node;
}
flowAnalysis.for_bodyBegin(node, node.condition);
StatementInferenceResult bodyResult = inferStatement(node.body);
if (bodyResult.hasChanged) {
node.body = bodyResult.statement..parent = node;
}
flowAnalysis.for_updaterBegin();
for (int index = 0; index < node.updates.length; index++) {
ExpressionInferenceResult updateResult = inferExpression(
node.updates[index], const UnknownType(),
isVoidAllowed: true);
node.updates[index] = updateResult.expression..parent = node;
}
flowAnalysis.for_end();
return const StatementInferenceResult();
}
FunctionType visitFunctionNode(FunctionNode node, DartType? typeContext,
DartType? returnContext, int returnTypeInstrumentationOffset) {
return inferLocalFunction(this, node, typeContext,
returnTypeInstrumentationOffset, returnContext);
}
@override
StatementInferenceResult visitFunctionDeclaration(
covariant FunctionDeclarationImpl node) {
bool oldInTryOrLocalFunction = _inTryOrLocalFunction;
_inTryOrLocalFunction = true;
VariableDeclaration variable = node.variable;
flowAnalysis.functionExpression_begin(node);
inferMetadata(this, variable, variable.annotations);
DartType? returnContext =
node.hasImplicitReturnType ? null : node.function.returnType;
FunctionType inferredType =
visitFunctionNode(node.function, null, returnContext, node.fileOffset);
if (dataForTesting != null && node.hasImplicitReturnType) {
dataForTesting!.typeInferenceResult.inferredVariableTypes[node] =
inferredType.returnType;
}
variable.type = inferredType;
flowAnalysis.declare(variable, variable.type, initialized: true);
flowAnalysis.functionExpression_end();
_inTryOrLocalFunction = oldInTryOrLocalFunction;
return const StatementInferenceResult();
}
@override
ExpressionInferenceResult visitFunctionExpression(
FunctionExpression node, DartType typeContext) {
bool oldInTryOrLocalFunction = _inTryOrLocalFunction;
_inTryOrLocalFunction = true;
flowAnalysis.functionExpression_begin(node);
FunctionType inferredType =
visitFunctionNode(node.function, typeContext, null, node.fileOffset);
if (dataForTesting != null) {
dataForTesting!.typeInferenceResult.inferredVariableTypes[node] =
inferredType.returnType;
}
flowAnalysis.functionExpression_end();
_inTryOrLocalFunction = oldInTryOrLocalFunction;
return new ExpressionInferenceResult(inferredType, node);
}
ExpressionInferenceResult visitIfNullExpression(
IfNullExpression node, DartType typeContext) {
// 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?`.
ExpressionInferenceResult lhsResult = inferExpression(
node.left, computeNullable(typeContext),
isVoidAllowed: false);
DartType t1 = lhsResult.inferredType;
// This ends any shorting in `node.left`.
Expression left = lhsResult.expression;
flowAnalysis.ifNullExpression_rightBegin(node.left, t1);
// - Let `T2` be the type of `e2` inferred with context type `J`, where:
// - If `K` is `_` or `dynamic`, `J = T1`.
DartType j;
if (typeContext is UnknownType || typeContext is DynamicType) {
j = t1;
} else
// - Otherwise, `J = K`.
{
j = typeContext;
}
ExpressionInferenceResult rhsResult =
inferExpression(node.right, j, isVoidAllowed: true);
DartType t2 = rhsResult.inferredType;
flowAnalysis.ifNullExpression_end();
// - Let `T` be `UP(NonNull(T1), T2)`.
DartType nonNullT1 = t1.toNonNull();
DartType t = typeSchemaEnvironment.getStandardUpperBound(nonNullT1, t2,
isNonNullableByDefault: isNonNullableByDefault);
// - Let `S` be the greatest closure of `K`.
DartType s = computeGreatestClosure(typeContext);
DartType inferredType;
// If `inferenceUpdate3` is not enabled, then the type of `E` is `T`.
if (!libraryBuilder.libraryFeatures.inferenceUpdate3.isEnabled) {
inferredType = t;
} else
// - If `T <: S`, then the type of `E` is `T`.
if (typeSchemaEnvironment.isSubtypeOf(
t, s, SubtypeCheckMode.withNullabilities)) {
inferredType = t;
} else
// - Otherwise, if `NonNull(T1) <: S` and `T2 <: S`, then the type of `E` is
// `S`.
if (typeSchemaEnvironment.isSubtypeOf(
nonNullT1, s, SubtypeCheckMode.withNullabilities) &&
typeSchemaEnvironment.isSubtypeOf(
t2, s, SubtypeCheckMode.withNullabilities)) {
inferredType = s;
} else
// - Otherwise, the type of `E` is `T`.
{
inferredType = t;
}
Expression replacement;
if (left is ThisExpression) {
replacement = left;
} else {
VariableDeclaration variable = createVariable(left, t1);
Expression equalsNull = createEqualsNull(createVariableGet(variable),
fileOffset: lhsResult.expression.fileOffset);
VariableGet variableGet = createVariableGet(variable);
if (isNonNullableByDefault && !identical(nonNullT1, t1)) {
variableGet.promotedType = nonNullT1;
}
ConditionalExpression conditional = new ConditionalExpression(
equalsNull, rhsResult.expression, variableGet, inferredType)
..fileOffset = node.fileOffset;
replacement = new Let(variable, conditional)
..fileOffset = node.fileOffset;
}
return new ExpressionInferenceResult(inferredType, replacement);
}
@override
StatementInferenceResult visitIfStatement(IfStatement node) {
flowAnalysis.ifStatement_conditionBegin();
InterfaceType expectedType =
coreTypes.boolRawType(libraryBuilder.nonNullable);
ExpressionInferenceResult conditionResult =
inferExpression(node.condition, expectedType, isVoidAllowed: true);
Expression condition =
ensureAssignableResult(expectedType, conditionResult).expression;
node.condition = condition..parent = node;
flowAnalysis.ifStatement_thenBegin(condition, node);
StatementInferenceResult thenResult = inferStatement(node.then);
if (thenResult.hasChanged) {
node.then = thenResult.statement..parent = node;
}
if (node.otherwise != null) {
flowAnalysis.ifStatement_elseBegin();
StatementInferenceResult otherwiseResult =
inferStatement(node.otherwise!);
if (otherwiseResult.hasChanged) {
node.otherwise = otherwiseResult.statement..parent = node;
}
}
flowAnalysis.ifStatement_end(node.otherwise != null);
return const StatementInferenceResult();
}
@override
StatementInferenceResult visitIfCaseStatement(IfCaseStatement node) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
IfCaseStatementResult<DartType, InvalidExpression> analysisResult =
analyzeIfCaseStatement(node, node.expression, node.patternGuard.pattern,
node.patternGuard.guard, node.then, node.otherwise, {
for (VariableDeclaration variable
in node.patternGuard.pattern.declaredVariables)
variable.name!: variable
});
node.matchedValueType = analysisResult.matchedExpressionType;
assert(checkStack(node, stackBase, [
/* ifFalse = */ ValueKinds.StatementOrNull,
/* ifTrue = */ ValueKinds.Statement,
/* guard = */ ValueKinds.ExpressionOrNull,
/* pattern = */ ValueKinds.Pattern,
/* scrutinee = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite(NullValues.Statement);
if (!identical(node.otherwise, rewrite)) {
node.otherwise = (rewrite as Statement)..parent = node;
}
rewrite = popRewrite();
if (!identical(node.then, rewrite)) {
node.then = (rewrite as Statement)..parent = node;
}
rewrite = popRewrite(NullValues.Expression);
InvalidExpression? guardError = analysisResult.nonBooleanGuardError;
if (guardError != null) {
node.patternGuard.guard = guardError..parent = node.patternGuard;
} else {
if (!identical(node.patternGuard.guard, rewrite)) {
node.patternGuard.guard = (rewrite as Expression)
..parent = node.patternGuard;
}
if (analysisResult.guardType is DynamicType) {
node.patternGuard.guard = _createImplicitAs(
node.patternGuard.guard!.fileOffset,
node.patternGuard.guard!,
coreTypes.boolNonNullableRawType)
..parent = node.patternGuard;
}
}
rewrite = popRewrite();
if (!identical(node.patternGuard.pattern, rewrite)) {
node.patternGuard.pattern = (rewrite as Pattern)
..parent = node.patternGuard;
}
rewrite = popRewrite();
if (!identical(node.expression, rewrite)) {
node.expression = (rewrite as Expression)..parent = node;
}
assert(checkStack(node, stackBase, [/*empty*/]));
return const StatementInferenceResult();
}
ExpressionInferenceResult visitIntJudgment(
IntJudgment node, DartType typeContext) {
if (isDoubleContext(typeContext)) {
double? doubleValue = node.asDouble();
if (doubleValue != null) {
Expression replacement = new DoubleLiteral(doubleValue)
..fileOffset = node.fileOffset;
DartType inferredType =
coreTypes.doubleRawType(libraryBuilder.nonNullable);
return new ExpressionInferenceResult(inferredType, replacement);
}
}
Expression? error = checkWebIntLiteralsErrorIfUnexact(
node.value, node.literal, node.fileOffset);
if (error != null) {
return new ExpressionInferenceResult(const DynamicType(), error);
}
DartType inferredType = coreTypes.intRawType(libraryBuilder.nonNullable);
return new ExpressionInferenceResult(inferredType, node);
}
ExpressionInferenceResult visitShadowLargeIntLiteral(
ShadowLargeIntLiteral node, DartType typeContext) {
if (isDoubleContext(typeContext)) {
double? doubleValue = node.asDouble();
if (doubleValue != null) {
Expression replacement = new DoubleLiteral(doubleValue)
..fileOffset = node.fileOffset;
DartType inferredType =
coreTypes.doubleRawType(libraryBuilder.nonNullable);
return new ExpressionInferenceResult(inferredType, replacement);
}
}
int? intValue = node.asInt64();
if (intValue == null) {
Expression replacement = helper.buildProblem(
templateIntegerLiteralIsOutOfRange.withArguments(node.literal),
node.fileOffset,
node.literal.length);
return new ExpressionInferenceResult(const DynamicType(), replacement);
}
Expression? error = checkWebIntLiteralsErrorIfUnexact(
intValue, node.literal, node.fileOffset);
if (error != null) {
return new ExpressionInferenceResult(const DynamicType(), error);
}
Expression replacement = new IntLiteral(intValue);
DartType inferredType = coreTypes.intRawType(libraryBuilder.nonNullable);
return new ExpressionInferenceResult(inferredType, replacement);
}
InitializerInferenceResult visitShadowInvalidInitializer(
ShadowInvalidInitializer node) {
ExpressionInferenceResult initializerResult = inferExpression(
node.variable.initializer!, const UnknownType(),
isVoidAllowed: false);
node.variable.initializer = initializerResult.expression
..parent = node.variable;
return const SuccessfulInitializerInferenceResult();
}
InitializerInferenceResult visitShadowInvalidFieldInitializer(
ShadowInvalidFieldInitializer node) {
ExpressionInferenceResult initializerResult =
inferExpression(node.value, node.fieldType, isVoidAllowed: false);
node.value = initializerResult.expression..parent = node;
return const SuccessfulInitializerInferenceResult();
}
@override
ExpressionInferenceResult visitIsExpression(
IsExpression node, DartType typeContext) {
ExpressionInferenceResult operandResult = inferExpression(
node.operand, const UnknownType(),
isVoidAllowed: false);
node.operand = operandResult.expression..parent = node;
flowAnalysis.isExpression_end(
node, node.operand, /*isNot:*/ false, node.type);
return new ExpressionInferenceResult(
coreTypes.boolRawType(libraryBuilder.nonNullable), node);
}
@override
StatementInferenceResult visitLabeledStatement(LabeledStatement node) {
flowAnalysis.labeledStatement_begin(node);
StatementInferenceResult bodyResult = inferStatement(node.body);
flowAnalysis.labeledStatement_end();
if (bodyResult.hasChanged) {
node.body = bodyResult.statement..parent = node;
}
return const StatementInferenceResult();
}
DartType? getSpreadElementType(
DartType spreadType, DartType spreadTypeBound, bool isNullAware) {
if (coreTypes.isNull(spreadTypeBound)) {
if (isNonNullableByDefault) {
return isNullAware ? const NeverType.nonNullable() : null;
} else {
return isNullAware ? const NullType() : null;
}
}
if (spreadTypeBound is TypeDeclarationType) {
List<DartType>? supertypeArguments =
typeSchemaEnvironment.getTypeArgumentsAsInstanceOf(
spreadTypeBound, coreTypes.iterableClass);
if (supertypeArguments == null) {
return null;
}
return supertypeArguments.single;
} else if (spreadType is DynamicType) {
return const DynamicType();
} else if (coreTypes.isBottom(spreadType)) {
return const NeverType.nonNullable();
}
return null;
}
ExpressionInferenceResult _inferSpreadElement(
SpreadElement element,
DartType inferredTypeArgument,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes) {
ExpressionInferenceResult spreadResult = inferExpression(
element.expression,
new InterfaceType(
coreTypes.iterableClass,
libraryBuilder.nullableIfTrue(element.isNullAware),
<DartType>[inferredTypeArgument]),
isVoidAllowed: true);
element.expression = spreadResult.expression..parent = element;
DartType spreadType = spreadResult.inferredType;
inferredSpreadTypes[element.expression] = spreadType;
Expression replacement = element;
DartType spreadTypeBound = spreadType.nonTypeVariableBound;
DartType? spreadElementType =
getSpreadElementType(spreadType, spreadTypeBound, element.isNullAware);
if (spreadElementType == null) {
if (coreTypes.isNull(spreadTypeBound) && !element.isNullAware) {
replacement = helper.buildProblem(
templateNonNullAwareSpreadIsNull.withArguments(
spreadType, isNonNullableByDefault),
element.expression.fileOffset,
1);
} else {
if (isNonNullableByDefault &&
spreadType.isPotentiallyNullable &&
spreadType is! DynamicType &&
spreadType is! NullType &&
!element.isNullAware) {
Expression receiver = element.expression;
replacement = helper.buildProblem(
messageNullableSpreadError, receiver.fileOffset, 1,
context: getWhyNotPromotedContext(
flowAnalysis.whyNotPromoted(receiver)(),
element,
(type) => !type.isPotentiallyNullable));
}
replacement = helper.buildProblem(
templateSpreadTypeMismatch.withArguments(
spreadType, isNonNullableByDefault),
element.expression.fileOffset,
1);
_copyNonPromotionReasonToReplacement(element, replacement);
}
} else if (spreadTypeBound is InterfaceType) {
if (!isAssignable(inferredTypeArgument, spreadElementType)) {
if (isNonNullableByDefault) {
IsSubtypeOf subtypeCheckResult =
typeSchemaEnvironment.performNullabilityAwareSubtypeCheck(
spreadElementType, inferredTypeArgument);
if (subtypeCheckResult.isSubtypeWhenIgnoringNullabilities()) {
if (spreadElementType == subtypeCheckResult.subtype &&
inferredTypeArgument == subtypeCheckResult.supertype) {
replacement = helper.buildProblem(
templateSpreadElementTypeMismatchNullability.withArguments(
spreadElementType,
inferredTypeArgument,
isNonNullableByDefault),
element.expression.fileOffset,
1);
} else {
replacement = helper.buildProblem(
templateSpreadElementTypeMismatchPartNullability
.withArguments(
spreadElementType,
inferredTypeArgument,
subtypeCheckResult.subtype!,
subtypeCheckResult.supertype!,
isNonNullableByDefault),
element.expression.fileOffset,
1);
}
} else {
replacement = helper.buildProblem(
templateSpreadElementTypeMismatch.withArguments(
spreadElementType,
inferredTypeArgument,
isNonNullableByDefault),
element.expression.fileOffset,
1);
}
} else {
replacement = helper.buildProblem(
templateSpreadElementTypeMismatch.withArguments(spreadElementType,
inferredTypeArgument, isNonNullableByDefault),
element.expression.fileOffset,
1);
}
}
if (isNonNullableByDefault &&
spreadType.isPotentiallyNullable &&
spreadType is! DynamicType &&
spreadType is! NullType &&
!element.isNullAware) {
Expression receiver = element.expression;
replacement = helper.buildProblem(
messageNullableSpreadError, receiver.fileOffset, 1,
context: getWhyNotPromotedContext(
flowAnalysis.whyNotPromoted(receiver)(),
element,
(type) => !type.isPotentiallyNullable));
_copyNonPromotionReasonToReplacement(element, replacement);
}
}
// Use 'dynamic' for error recovery.
element.elementType = spreadElementType ?? const DynamicType();
return new ExpressionInferenceResult(element.elementType!, replacement);
}
ExpressionInferenceResult _inferIfElement(
IfElement element,
DartType inferredTypeArgument,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes) {
flowAnalysis.ifStatement_conditionBegin();
DartType boolType = coreTypes.boolRawType(libraryBuilder.nonNullable);
ExpressionInferenceResult conditionResult =
inferExpression(element.condition, boolType, isVoidAllowed: false);
Expression condition =
ensureAssignableResult(boolType, conditionResult).expression;
element.condition = condition..parent = element;
flowAnalysis.ifStatement_thenBegin(condition, element);
ExpressionInferenceResult thenResult = inferElement(element.then,
inferredTypeArgument, inferredSpreadTypes, inferredConditionTypes);
element.then = thenResult.expression..parent = element;
ExpressionInferenceResult? otherwiseResult;
if (element.otherwise != null) {
flowAnalysis.ifStatement_elseBegin();
otherwiseResult = inferElement(element.otherwise!, inferredTypeArgument,
inferredSpreadTypes, inferredConditionTypes);
element.otherwise = otherwiseResult.expression..parent = element;
}
flowAnalysis.ifStatement_end(element.otherwise != null);
return new ExpressionInferenceResult(
otherwiseResult == null
? thenResult.inferredType
: typeSchemaEnvironment.getStandardUpperBound(
thenResult.inferredType, otherwiseResult.inferredType,
isNonNullableByDefault: isNonNullableByDefault),
element);
}
ExpressionInferenceResult _inferIfCaseElement(
IfCaseElement element,
DartType inferredTypeArgument,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes) {
int? stackBase;
assert(checkStackBase(element, stackBase = stackHeight));
ListAndSetElementInferenceContext context =
new ListAndSetElementInferenceContext(
inferredTypeArgument: inferredTypeArgument,
inferredSpreadTypes: inferredSpreadTypes,
inferredConditionTypes: inferredConditionTypes);
IfCaseStatementResult<DartType, InvalidExpression> analysisResult =
analyzeIfCaseElement(
node: element,
expression: element.expression,
pattern: element.patternGuard.pattern,
variables: {
for (VariableDeclaration variable
in element.patternGuard.pattern.declaredVariables)
variable.name!: variable
},
guard: element.patternGuard.guard,
ifTrue: element.then,
ifFalse: element.otherwise,
context: context);
element.matchedValueType = analysisResult.matchedExpressionType;
assert(checkStack(element, stackBase, [
/* ifFalse = */ ValueKinds.ExpressionOrNull,
/* ifTrue = */ ValueKinds.Expression,
/* guard = */ ValueKinds.ExpressionOrNull,
/* pattern = */ ValueKinds.Pattern,
/* scrutinee = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite(NullValues.Expression);
if (!identical(element.otherwise, rewrite)) {
element.otherwise = (rewrite as Expression?)?..parent = element;
}
rewrite = popRewrite();
if (!identical(element.then, rewrite)) {
element.then = (rewrite as Expression)..parent = element;
}
PatternGuard patternGuard = element.patternGuard;
rewrite = popRewrite(NullValues.Expression);
InvalidExpression? guardError = analysisResult.nonBooleanGuardError;
if (guardError != null) {
patternGuard.guard = guardError..parent = patternGuard;
} else {
if (!identical(patternGuard.guard, rewrite)) {
patternGuard.guard = (rewrite as Expression?)?..parent = patternGuard;
}
if (analysisResult.guardType is DynamicType) {
patternGuard.guard = _createImplicitAs(patternGuard.guard!.fileOffset,
patternGuard.guard!, coreTypes.boolNonNullableRawType)
..parent = patternGuard;
}
}
rewrite = popRewrite();
if (!identical(patternGuard.pattern, rewrite)) {
patternGuard.pattern = (rewrite as Pattern)..parent = patternGuard;
}
rewrite = popRewrite();
if (!identical(element.expression, rewrite)) {
element.expression = (rewrite as Expression)..parent = patternGuard;
}
DartType thenType = context.inferredConditionTypes[element.then]!;
DartType? otherwiseType = element.otherwise == null
? null
: context.inferredConditionTypes[element.otherwise!]!;
return new ExpressionInferenceResult(
otherwiseType == null
? thenType
: typeSchemaEnvironment.getStandardUpperBound(
thenType, otherwiseType,
isNonNullableByDefault: isNonNullableByDefault),
element);
}
ExpressionInferenceResult _inferForElement(
ForElement element,
DartType inferredTypeArgument,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes) {
if (element is PatternForElement) {
int? stackBase;
assert(checkStackBase(element, stackBase = stackHeight));
PatternVariableDeclaration patternVariableDeclaration =
element.patternVariableDeclaration;
PatternVariableDeclarationAnalysisResult<DartType, DartType>
analysisResult = analyzePatternVariableDeclaration(
patternVariableDeclaration,
patternVariableDeclaration.pattern,
patternVariableDeclaration.initializer,
isFinal: patternVariableDeclaration.isFinal);
patternVariableDeclaration.matchedValueType =
analysisResult.initializerType;
assert(checkStack(element, stackBase, [
/* pattern = */ ValueKinds.Pattern,
/* initializer = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite(NullValues.Expression);
if (!identical(patternVariableDeclaration.pattern, rewrite)) {
patternVariableDeclaration.pattern = (rewrite as Pattern)
..parent = patternVariableDeclaration;
}
rewrite = popRewrite();
if (!identical(patternVariableDeclaration.initializer, rewrite)) {
patternVariableDeclaration.initializer = (rewrite as Expression)
..parent = patternVariableDeclaration;
}
List<VariableDeclaration> declaredVariables =
patternVariableDeclaration.pattern.declaredVariables;
assert(declaredVariables.length == element.intermediateVariables.length);
assert(declaredVariables.length == element.variables.length);
for (int i = 0; i < declaredVariables.length; i++) {
DartType type = declaredVariables[i].type;
element.intermediateVariables[i].type = type;
element.variables[i].type = type;
}
}
// TODO(johnniwinther): Use _visitStatements instead.
List<VariableDeclaration>? variables;
for (int index = 0; index < element.variables.length; index++) {
VariableDeclaration variable = element.variables[index];
if (variable.name == null) {
if (variable.initializer != null) {
ExpressionInferenceResult initializerResult = inferExpression(
variable.initializer!, variable.type,
isVoidAllowed: true);
variable.initializer = initializerResult.expression
..parent = variable;
variable.type = initializerResult.inferredType;
}
} else {
StatementInferenceResult variableResult = inferStatement(variable);
if (variableResult.hasChanged) {
if (variables == null) {
variables = <VariableDeclaration>[];
variables.addAll(element.variables.sublist(0, index));
}
if (variableResult.statementCount == 1) {
variables.add(variableResult.statement as VariableDeclaration);
} else {
for (Statement variable in variableResult.statements) {
variables.add(variable as VariableDeclaration);
}
}
} else if (variables != null) {
variables.add(variable);
}
}
}
if (variables != null) {
element.variables.clear();
element.variables.addAll(variables);
setParents(variables, element);
}
flowAnalysis.for_conditionBegin(element);
if (element.condition != null) {
ExpressionInferenceResult conditionResult = inferExpression(
element.condition!, coreTypes.boolRawType(libraryBuilder.nonNullable),
isVoidAllowed: false);
element.condition = conditionResult.expression..parent = element;
inferredConditionTypes[element.condition!] = conditionResult.inferredType;
}
flowAnalysis.for_bodyBegin(null, element.condition);
ExpressionInferenceResult bodyResult = inferElement(element.body,
inferredTypeArgument, inferredSpreadTypes, inferredConditionTypes);
element.body = bodyResult.expression..parent = element;
flowAnalysis.for_updaterBegin();
for (int index = 0; index < element.updates.length; index++) {
ExpressionInferenceResult updateResult = inferExpression(
element.updates[index], const UnknownType(),
isVoidAllowed: true);
element.updates[index] = updateResult.expression..parent = element;
}
flowAnalysis.for_end();
return new ExpressionInferenceResult(bodyResult.inferredType, element);
}
ExpressionInferenceResult _inferForInElement(
ForInElement element,
DartType inferredTypeArgument,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes) {
ForInResult result;
if (element.variable.name == null) {
result = handleForInWithoutVariable(
element,
element.variable,
element.iterable,
element.syntheticAssignment,
element.expressionEffects,
isAsync: element.isAsync,
hasProblem: element.problem != null);
} else {
result = handleForInDeclaringVariable(element, element.variable,
element.iterable, element.expressionEffects,
isAsync: element.isAsync);
}
element.variable = result.variable..parent = element;
element.iterable = result.iterable..parent = element;
// TODO(johnniwinther): Use ?.. here instead.
element.syntheticAssignment = result.syntheticAssignment;
result.syntheticAssignment?.parent = element;
// TODO(johnniwinther): Use ?.. here instead.
element.expressionEffects = result.expressionSideEffects;
result.expressionSideEffects?.parent = element;
if (element.problem != null) {
ExpressionInferenceResult problemResult = inferExpression(
element.problem!, const UnknownType(),
isVoidAllowed: true);
element.problem = problemResult.expression..parent = element;
}
ExpressionInferenceResult bodyResult = inferElement(element.body,
inferredTypeArgument, inferredSpreadTypes, inferredConditionTypes);
element.body = bodyResult.expression..parent = element;
// This is matched by the call to [forEach_bodyBegin] in
// [handleForInWithoutVariable] or [handleForInDeclaringVariable].
flowAnalysis.forEach_end();
return new ExpressionInferenceResult(bodyResult.inferredType, element);
}
ExpressionInferenceResult inferElement(
Expression element,
DartType inferredTypeArgument,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes) {
if (element is SpreadElement) {
return _inferSpreadElement(element, inferredTypeArgument,
inferredSpreadTypes, inferredConditionTypes);
} else if (element is IfElement) {
return _inferIfElement(element, inferredTypeArgument, inferredSpreadTypes,
inferredConditionTypes);
} else if (element is IfCaseElement) {
return _inferIfCaseElement(element, inferredTypeArgument,
inferredSpreadTypes, inferredConditionTypes);
} else if (element is ForElement) {
return _inferForElement(element, inferredTypeArgument,
inferredSpreadTypes, inferredConditionTypes);
} else if (element is ForInElement) {
return _inferForInElement(element, inferredTypeArgument,
inferredSpreadTypes, inferredConditionTypes);
} else {
ExpressionInferenceResult result =
inferExpression(element, inferredTypeArgument, isVoidAllowed: true);
if (inferredTypeArgument is! UnknownType) {
result = ensureAssignableResult(inferredTypeArgument, result,
isVoidAllowed: inferredTypeArgument is VoidType);
}
return result;
}
}
void _copyNonPromotionReasonToReplacement(
TreeNode oldNode, TreeNode replacement) {
if (!identical(oldNode, replacement) &&
dataForTesting?.flowAnalysisResult != null) {
dataForTesting!.flowAnalysisResult.nonPromotionReasons[replacement] =
dataForTesting!.flowAnalysisResult.nonPromotionReasons[oldNode]!;
}
}
void checkElement(
Expression item,
Expression parent,
DartType typeArgument,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes) {
if (item is SpreadElement) {
DartType? spreadType = inferredSpreadTypes[item.expression];
if (spreadType is DynamicType) {
Expression expression = ensureAssignable(
coreTypes.iterableRawType(
libraryBuilder.nullableIfTrue(item.isNullAware)),
spreadType,
item.expression);
item.expression = expression..parent = item;
}
} else if (item is IfElement) {
checkElement(item.then, item, typeArgument, inferredSpreadTypes,
inferredConditionTypes);
if (item.otherwise != null) {
checkElement(item.otherwise!, item, typeArgument, inferredSpreadTypes,
inferredConditionTypes);
}
} else if (item is ForElement) {
if (item.condition != null) {
DartType conditionType = inferredConditionTypes[item.condition]!;
Expression condition = ensureAssignable(
coreTypes.boolRawType(libraryBuilder.nonNullable),
conditionType,
item.condition!);
item.condition = condition..parent = item;
}
checkElement(item.body, item, typeArgument, inferredSpreadTypes,
inferredConditionTypes);
} else if (item is ForInElement) {
checkElement(item.body, item, typeArgument, inferredSpreadTypes,
inferredConditionTypes);
} else {
// Do nothing. Assignability checks are done during type inference.
}
}
@override
ExpressionInferenceResult visitListLiteral(
ListLiteral node, DartType typeContext) {
Class listClass = coreTypes.listClass;
InterfaceType listType =
coreTypes.thisInterfaceType(listClass, libraryBuilder.nonNullable);
List<DartType>? inferredTypes;
DartType inferredTypeArgument;
bool inferenceNeeded = node.typeArgument is ImplicitTypeArgument;
List<DartType> formalTypes = [];
List<DartType> actualTypes = [];
Map<TreeNode, DartType> inferredSpreadTypes =
new Map<TreeNode, DartType>.identity();
Map<Expression, DartType> inferredConditionTypes =
new Map<Expression, DartType>.identity();
TypeConstraintGatherer? gatherer;
FreshStructuralParametersFromTypeParameters freshTypeParameters =
getFreshStructuralParametersFromTypeParameters(
listClass.typeParameters);
List<StructuralParameter> typeParametersToInfer =
freshTypeParameters.freshTypeParameters;
listType = freshTypeParameters.substitute(listType) as InterfaceType;
if (inferenceNeeded) {
gatherer = typeSchemaEnvironment.setupGenericTypeInference(
listType, typeParametersToInfer, typeContext,
isNonNullableByDefault: isNonNullableByDefault,
isConst: node.isConst,
typeOperations: operations,
inferenceResultForTesting: dataForTesting?.typeInferenceResult,
treeNodeForTesting: node);
inferredTypes = typeSchemaEnvironment.choosePreliminaryTypes(
gatherer, typeParametersToInfer, null,
isNonNullableByDefault: isNonNullableByDefault);
inferredTypeArgument = inferredTypes[0];
} else {
inferredTypeArgument = node.typeArgument;
}
for (int index = 0; index < node.expressions.length; ++index) {
ExpressionInferenceResult result = inferElement(node.expressions[index],
inferredTypeArgument, inferredSpreadTypes, inferredConditionTypes);
node.expressions[index] = result.expression..parent = node;
actualTypes.add(result.inferredType);
if (inferenceNeeded) {
formalTypes.add(listType.typeArguments[0]);
}
}
if (inferenceNeeded) {
gatherer!.constrainArguments(formalTypes, actualTypes,
treeNodeForTesting: node);
inferredTypes = typeSchemaEnvironment.chooseFinalTypes(
gatherer, typeParametersToInfer, inferredTypes!,
isNonNullableByDefault: isNonNullableByDefault);
if (dataForTesting != null) {
dataForTesting!.typeInferenceResult.inferredTypeArguments[node] =
inferredTypes;
}
inferredTypeArgument = inferredTypes[0];
instrumentation?.record(
uriForInstrumentation,
node.fileOffset,
'typeArgs',
new InstrumentationValueForTypeArgs([inferredTypeArgument]));
node.typeArgument = inferredTypeArgument;
}
for (int i = 0; i < node.expressions.length; i++) {
checkElement(node.expressions[i], node, node.typeArgument,
inferredSpreadTypes, inferredConditionTypes);
}
DartType inferredType = new InterfaceType(
listClass, libraryBuilder.nonNullable, [inferredTypeArgument]);
if (inferenceNeeded) {
if (!libraryBuilder.libraryFeatures.genericMetadata.isEnabled) {
checkGenericFunctionTypeArgument(node.typeArgument, node.fileOffset);
}
}
Expression result = _translateListLiteral(node);
return new ExpressionInferenceResult(inferredType, result);
}
@override
ExpressionInferenceResult visitRecordLiteral(
RecordLiteral node, DartType typeContext) {
// TODO(cstefantsova): Implement this method.
return new ExpressionInferenceResult(node.recordType, node);
}
@override
ExpressionInferenceResult visitLogicalExpression(
LogicalExpression node, DartType typeContext) {
InterfaceType boolType = coreTypes.boolRawType(libraryBuilder.nonNullable);
flowAnalysis.logicalBinaryOp_begin();
ExpressionInferenceResult leftResult =
inferExpression(node.left, boolType, isVoidAllowed: false);
Expression left = ensureAssignableResult(boolType, leftResult).expression;
node.left = left..parent = node;
flowAnalysis.logicalBinaryOp_rightBegin(node.left, node,
isAnd: node.operatorEnum == LogicalExpressionOperator.AND);
ExpressionInferenceResult rightResult =
inferExpression(node.right, boolType, isVoidAllowed: false);
Expression right = ensureAssignableResult(boolType, rightResult).expression;
node.right = right..parent = node;
flowAnalysis.logicalBinaryOp_end(node, node.right,
isAnd: node.operatorEnum == LogicalExpressionOperator.AND);
return new ExpressionInferenceResult(boolType, node);
}
Expression _translateNonConstListOrSet(
Expression node, DartType elementType, List<Expression> elements,
{bool isSet = false}) {
assert((node is ListLiteral && !node.isConst) ||
(node is SetLiteral && !node.isConst));
// Translate elements in place up to the first non-expression, if any.
int index = 0;
for (; index < elements.length; ++index) {
if (elements[index] is ControlFlowElement) break;
}
// If there were only expressions, we are done.
if (index == elements.length) {
if (node is SetLiteral) {
return _lowerSetLiteral(node);
}
return node;
}
InterfaceType receiverType = isSet
? typeSchemaEnvironment.setType(elementType, libraryBuilder.nonNullable)
: typeSchemaEnvironment.listType(
elementType, libraryBuilder.nonNullable);
VariableDeclaration? result;
if (index == 0 && elements[index] is SpreadElement) {
SpreadElement initialSpread = elements[index] as SpreadElement;
final bool typeMatches = initialSpread.elementType != null &&
typeSchemaEnvironment.isSubtypeOf(initialSpread.elementType!,
elementType, SubtypeCheckMode.withNullabilities);
if (typeMatches && !initialSpread.isNullAware) {
// Create a list or set of the initial spread element.
Expression value = initialSpread.expression;
index++;
if (isSet) {
result = _createVariable(
new StaticInvocation(
engine.setOf,
new Arguments([value], types: [elementType])
..fileOffset = node.fileOffset)
..fileOffset = node.fileOffset,
receiverType);
} else {
result = _createVariable(
new StaticInvocation(
engine.listOf,
new Arguments([value], types: [elementType])
..fileOffset = node.fileOffset)
..fileOffset = node.fileOffset,
receiverType);
}
}
}
List<Statement>? body;
if (result == null) {
// Create a list or set with the elements up to the first non-expression.
if (isSet) {
if (libraryBuilder.loader.target.backendTarget.supportsSetLiterals) {
// Include the elements up to the first non-expression in the set
// literal.
result = _createVariable(
_lowerSetLiteral(_createSetLiteral(
node.fileOffset, elementType, elements.sublist(0, index))),
receiverType);
} else {
// TODO(johnniwinther): When all the back ends handle set literals we
// can use remove this branch.
// Create an empty set using the [setFactory] constructor.
result = _createVariable(
new StaticInvocation(
engine.setFactory,
new Arguments([], types: [elementType])
..fileOffset = node.fileOffset)
..fileOffset = node.fileOffset,
receiverType);
body = [result];
// Add the elements up to the first non-expression.
for (int j = 0; j < index; ++j) {
_addExpressionElement(elements[j], receiverType, result, body,
isSet: isSet);
}
}
} else {
// Include the elements up to the first non-expression in the list
// literal.
result = _createVariable(
_createListLiteral(
node.fileOffset, elementType, elements.sublist(0, index)),
receiverType);
}
}
body ??= [result];
// Translate the elements starting with the first non-expression.
for (; index < elements.length; ++index) {
_translateElement(
elements[index], receiverType, elementType, result, body,
isSet: isSet);
}
return _createBlockExpression(
node.fileOffset, _createBlock(body), _createVariableGet(result));
}
void _translateElement(Expression element, InterfaceType receiverType,
DartType elementType, VariableDeclaration result, List<Statement> body,
{required bool isSet}) {
if (element is SpreadElement) {
_translateSpreadElement(element, receiverType, elementType, result, body,
isSet: isSet);
} else if (element is IfElement) {
_translateIfElement(element, receiverType, elementType, result, body,
isSet: isSet);
} else if (element is IfCaseElement) {
_translateIfCaseElement(element, receiverType, elementType, result, body,
isSet: isSet);
} else if (element is ForElement) {
if (element is PatternForElement) {
_translatePatternForElement(
element, receiverType, elementType, result, body,
isSet: isSet);
} else {
_translateForElement(element, receiverType, elementType, result, body,
isSet: isSet);
}
} else if (element is ForInElement) {
_translateForInElement(element, receiverType, elementType, result, body,
isSet: isSet);
} else {
_addExpressionElement(element, receiverType, result, body, isSet: isSet);
}
}
void _addExpressionElement(Expression element, InterfaceType receiverType,
VariableDeclaration result, List<Statement> body,
{required bool isSet}) {
body.add(_createExpressionStatement(_createAdd(
// Don't make a mess of jumping around (and make scope building
// impossible).
_createVariableGet(result)..fileOffset = TreeNode.noOffset,
receiverType,
element,
isSet: isSet)));
}
void _translateIfElement(IfElement element, InterfaceType receiverType,
DartType elementType, VariableDeclaration result, List<Statement> body,
{required bool isSet}) {
List<Statement> thenStatements = [];
_translateElement(
element.then, receiverType, elementType, result, thenStatements,
isSet: isSet);
List<Statement>? elseStatements;
if (element.otherwise != null) {
_translateElement(element.otherwise!, receiverType, elementType, result,
elseStatements = <Statement>[],
isSet: isSet);
}
Statement thenBody = thenStatements.length == 1
? thenStatements.first
: _createBlock(thenStatements);
Statement? elseBody;
if (elseStatements != null && elseStatements.isNotEmpty) {
elseBody = elseStatements.length == 1
? elseStatements.first
: _createBlock(elseStatements);
}
IfStatement ifStatement =
_createIf(element.fileOffset, element.condition, thenBody, elseBody);
libraryBuilder.loader.dataForTesting?.registerAlias(element, ifStatement);
body.add(ifStatement);
}
void _translateIfCaseElement(
IfCaseElement element,
InterfaceType receiverType,
DartType elementType,
VariableDeclaration result,
List<Statement> body,
{required bool isSet}) {
List<Statement> thenStatements = [];
_translateElement(
element.then, receiverType, elementType, result, thenStatements,
isSet: isSet);
List<Statement>? elseStatements;
if (element.otherwise != null) {
_translateElement(element.otherwise!, receiverType, elementType, result,
elseStatements = <Statement>[],
isSet: isSet);
}
Statement thenBody = thenStatements.length == 1
? thenStatements.first
: _createBlock(thenStatements);
Statement? elseBody;
if (elseStatements != null && elseStatements.isNotEmpty) {
elseBody = elseStatements.length == 1
? elseStatements.first
: _createBlock(elseStatements);
}
IfCaseStatement ifCaseStatement = _createIfCase(
element.fileOffset,
element.expression,
element.matchedValueType!,
element.patternGuard,
thenBody,
elseBody);
libraryBuilder.loader.dataForTesting
?.registerAlias(element, ifCaseStatement);
body.addAll(element.prelude);
body.add(ifCaseStatement);
}
void _translateForElement(ForElement element, InterfaceType receiverType,
DartType elementType, VariableDeclaration result, List<Statement> body,
{required bool isSet}) {
List<Statement> statements = <Statement>[];
_translateElement(
element.body, receiverType, elementType, result, statements,
isSet: isSet);
Statement loopBody =
statements.length == 1 ? statements.first : _createBlock(statements);
ForStatement loop = _createForStatement(element.fileOffset,
element.variables, element.condition, element.updates, loopBody);
libraryBuilder.loader.dataForTesting?.registerAlias(element, loop);
body.add(loop);
}
void _translatePatternForElement(
PatternForElement element,
InterfaceType receiverType,
DartType elementType,
VariableDeclaration result,
List<Statement> body,
{required bool isSet}) {
List<Statement> statements = <Statement>[];
_translateElement(
element.body, receiverType, elementType, result, statements,
isSet: isSet);
Statement loopBody =
statements.length == 1 ? statements.first : _createBlock(statements);
ForStatement loop = _createForStatement(element.fileOffset,
element.variables, element.condition, element.updates, loopBody);
libraryBuilder.loader.dataForTesting?.registerAlias(element, loop);
body.add(element.patternVariableDeclaration);
body.addAll(element.intermediateVariables);
body.add(loop);
}
void _translateForInElement(ForInElement element, InterfaceType receiverType,
DartType elementType, VariableDeclaration result, List<Statement> body,
{required bool isSet}) {
List<Statement> statements;
Statement? prologue = element.prologue;
if (prologue == null) {
statements = <Statement>[];
} else {
statements =
prologue is Block ? prologue.statements : <Statement>[prologue];
}
_translateElement(
element.body, receiverType, elementType, result, statements,
isSet: isSet);
Statement loopBody =
statements.length == 1 ? statements.first : _createBlock(statements);
if (element.problem != null) {
body.add(_createExpressionStatement(element.problem!));
}
ForInStatement loop = _createForInStatement(
element.fileOffset, element.variable, element.iterable, loopBody,
isAsync: element.isAsync);
libraryBuilder.loader.dataForTesting?.registerAlias(element, loop);
body.add(loop);
}
void _translateSpreadElement(
SpreadElement element,
InterfaceType receiverType,
DartType elementType,
VariableDeclaration result,
List<Statement> body,
{required bool isSet}) {
Expression value = element.expression;
final bool typeMatches = element.elementType != null &&
typeSchemaEnvironment.isSubtypeOf(element.elementType!, elementType,
SubtypeCheckMode.withNullabilities);
if (typeMatches) {
// If the type guarantees that all elements are of the required type, use
// a single 'addAll' call instead of a for-loop with calls to 'add'.
// Null-aware spreads require testing the subexpression's value.
VariableDeclaration? temp;
if (element.isNullAware) {
temp = _createVariable(
value,
typeSchemaEnvironment.iterableType(
elementType, libraryBuilder.nullable));
body.add(temp);
value = _createNullCheckedVariableGet(temp);
}
Statement statement = _createExpressionStatement(_createAddAll(
// Don't make a mess of jumping around (and make scope building
// impossible).
_createVariableGet(result)..fileOffset = TreeNode.noOffset,
receiverType,
value,
isSet));
if (element.isNullAware) {
statement = _createIf(
temp!.fileOffset,
_createEqualsNull(_createVariableGet(temp), notEquals: true),
statement);
}
body.add(statement);
} else {
// Null-aware spreads require testing the subexpression's value.
VariableDeclaration? temp;
if (element.isNullAware) {
temp = _createVariable(
value,
typeSchemaEnvironment.iterableType(
const DynamicType(), libraryBuilder.nullable));
body.add(temp);
value = _createNullCheckedVariableGet(temp);
}
VariableDeclaration variable =
_createForInVariable(element.fileOffset, const DynamicType());
VariableDeclaration castedVar = _createVariable(
_createImplicitAs(element.expression.fileOffset,
_createVariableGet(variable), elementType),
elementType);
Statement loopBody = _createBlock(<Statement>[
castedVar,
_createExpressionStatement(_createAdd(
// Don't make a mess of jumping around (and make scope building
// impossible).
_createVariableGet(result)..fileOffset = TreeNode.noOffset,
receiverType,
_createVariableGet(castedVar),
isSet: isSet))
]);
Statement statement =
_createForInStatement(element.fileOffset, variable, value, loopBody);
if (element.isNullAware) {
statement = _createIf(
temp!.fileOffset,
_createEqualsNull(_createVariableGet(temp), notEquals: true),
statement);
}
body.add(statement);
}
}
Expression _translateListLiteral(ListLiteral node) {
if (node.isConst) {
return _translateConstListOrSet(node, node.typeArgument, node.expressions,
isSet: false);
} else {
return _translateNonConstListOrSet(
node, node.typeArgument, node.expressions,
isSet: false);
}
}
Expression _translateSetLiteral(SetLiteral node) {
if (node.isConst) {
return _translateConstListOrSet(node, node.typeArgument, node.expressions,
isSet: true);
} else {
return _translateNonConstListOrSet(
node, node.typeArgument, node.expressions,
isSet: true);
}
}
Expression _translateMapLiteral(MapLiteral node) {
if (node.isConst) {
return _translateConstMap(node);
} else {
return _translateNonConstMap(node);
}
}
Expression _translateNonConstMap(MapLiteral node) {
assert(!node.isConst);
// Translate entries in place up to the first control-flow entry, if any.
int index = 0;
for (; index < node.entries.length; ++index) {
if (node.entries[index] is ControlFlowMapEntry) break;
node.entries[index] = node.entries[index]..parent = node;
}
// If there were no control-flow entries we are done.
if (index == node.entries.length) return node;
// Build a block expression and create an empty map.
InterfaceType receiverType = typeSchemaEnvironment.mapType(
node.keyType, node.valueType, libraryBuilder.nonNullable);
VariableDeclaration? result;
if (index == 0 && node.entries[index] is SpreadMapEntry) {
SpreadMapEntry initialSpread = node.entries[index] as SpreadMapEntry;
final InterfaceType entryType = new InterfaceType(engine.mapEntryClass,
libraryBuilder.nonNullable, <DartType>[node.keyType, node.valueType]);
final bool typeMatches = initialSpread.entryType != null &&
typeSchemaEnvironment.isSubtypeOf(initialSpread.entryType!, entryType,
SubtypeCheckMode.withNullabilities);
if (typeMatches && !initialSpread.isNullAware) {
{
// Create a map of the initial spread element.
Expression value = initialSpread.expression;
index++;
result = _createVariable(
new StaticInvocation(
engine.mapOf,
new Arguments([value], types: [node.keyType, node.valueType])
..fileOffset = node.fileOffset)
..fileOffset = node.fileOffset,
receiverType);
}
}
}
List<Statement>? body;
if (result == null) {
result = _createVariable(
_createMapLiteral(node.fileOffset, node.keyType, node.valueType, []),
receiverType);
body = [result];
// Add all the entries up to the first control-flow entry.
for (int j = 0; j < index; ++j) {
_addNormalEntry(node.entries[j], receiverType, result, body);
}
}
body ??= [result];
// Translate the elements starting with the first non-expression.
for (; index < node.entries.length; ++index) {
_translateEntry(node.entries[index], receiverType, node.keyType,
node.valueType, result, body);
}
return _createBlockExpression(
node.fileOffset, _createBlock(body), _createVariableGet(result));
}
void _translateEntry(
MapLiteralEntry entry,
InterfaceType receiverType,
DartType keyType,
DartType valueType,
VariableDeclaration result,
List<Statement> body) {
if (entry is SpreadMapEntry) {
_translateSpreadEntry(
entry, receiverType, keyType, valueType, result, body);
} else if (entry is IfMapEntry) {
_translateIfEntry(entry, receiverType, keyType, valueType, result, body);
} else if (entry is IfCaseMapEntry) {
_translateIfCaseEntry(
entry, receiverType, keyType, valueType, result, body);
} else if (entry is ForMapEntry) {
if (entry is PatternForMapEntry) {
_translatePatternForEntry(
entry, receiverType, keyType, valueType, result, body);
} else {
_translateForEntry(
entry, receiverType, keyType, valueType, result, body);
}
} else if (entry is ForInMapEntry) {
_translateForInEntry(
entry, receiverType, keyType, valueType, result, body);
} else {
_addNormalEntry(entry, receiverType, result, body);
}
}
void _addNormalEntry(MapLiteralEntry entry, InterfaceType receiverType,
VariableDeclaration result, List<Statement> body) {
body.add(_createExpressionStatement(_createIndexSet(
entry.fileOffset,
_createVariableGet(result)..fileOffset = TreeNode.noOffset,
receiverType,
entry.key,
entry.value)));
}
void _translateIfEntry(
IfMapEntry entry,
InterfaceType receiverType,
DartType keyType,
DartType valueType,
VariableDeclaration result,
List<Statement> body) {
List<Statement> thenBody = [];
_translateEntry(
entry.then, receiverType, keyType, valueType, result, thenBody);
List<Statement>? elseBody;
if (entry.otherwise != null) {
_translateEntry(entry.otherwise!, receiverType, keyType, valueType,
result, elseBody = <Statement>[]);
}
Statement thenStatement =
thenBody.length == 1 ? thenBody.first : _createBlock(thenBody);
Statement? elseStatement;
if (elseBody != null && elseBody.isNotEmpty) {
elseStatement =
elseBody.length == 1 ? elseBody.first : _createBlock(elseBody);
}
IfStatement ifStatement = _createIf(
entry.fileOffset, entry.condition, thenStatement, elseStatement);
libraryBuilder.loader.dataForTesting?.registerAlias(entry, ifStatement);
body.add(ifStatement);
}
void _translateIfCaseEntry(
IfCaseMapEntry entry,
InterfaceType receiverType,
DartType keyType,
DartType valueType,
VariableDeclaration result,
List<Statement> body) {
List<Statement> thenBody = [];
_translateEntry(
entry.then, receiverType, keyType, valueType, result, thenBody);
List<Statement>? elseBody;
if (entry.otherwise != null) {
_translateEntry(entry.otherwise!, receiverType, keyType, valueType,
result, elseBody = <Statement>[]);
}
Statement thenStatement =
thenBody.length == 1 ? thenBody.first : _createBlock(thenBody);
Statement? elseStatement;
if (elseBody != null && elseBody.isNotEmpty) {
elseStatement =
elseBody.length == 1 ? elseBody.first : _createBlock(elseBody);
}
IfCaseStatement ifStatement = _createIfCase(
entry.fileOffset,
entry.expression,
entry.matchedValueType!,
entry.patternGuard,
thenStatement,
elseStatement);
libraryBuilder.loader.dataForTesting?.registerAlias(entry, ifStatement);
body.addAll(entry.prelude);
body.add(ifStatement);
}
void _translateForEntry(
ForMapEntry entry,
InterfaceType receiverType,
DartType keyType,
DartType valueType,
VariableDeclaration result,
List<Statement> body) {
List<Statement> statements = <Statement>[];
_translateEntry(
entry.body, receiverType, keyType, valueType, result, statements);
Statement loopBody =
statements.length == 1 ? statements.first : _createBlock(statements);
ForStatement loop = _createForStatement(entry.fileOffset, entry.variables,
entry.condition, entry.updates, loopBody);
libraryBuilder.loader.dataForTesting?.registerAlias(entry, loop);
body.add(loop);
}
void _translatePatternForEntry(
PatternForMapEntry entry,
InterfaceType receiverType,
DartType keyType,
DartType valueType,
VariableDeclaration result,
List<Statement> body) {
List<Statement> statements = <Statement>[];
_translateEntry(
entry.body, receiverType, keyType, valueType, result, statements);
Statement loopBody =
statements.length == 1 ? statements.first : _createBlock(statements);
ForStatement loop = _createForStatement(entry.fileOffset, entry.variables,
entry.condition, entry.updates, loopBody);
libraryBuilder.loader.dataForTesting?.registerAlias(entry, loop);
body.add(entry.patternVariableDeclaration);
body.addAll(entry.intermediateVariables);
body.add(loop);
}
void _translateForInEntry(
ForInMapEntry entry,
InterfaceType receiverType,
DartType keyType,
DartType valueType,
VariableDeclaration result,
List<Statement> body) {
List<Statement> statements;
Statement? prologue = entry.prologue;
if (prologue == null) {
statements = <Statement>[];
} else {
statements =
prologue is Block ? prologue.statements : <Statement>[prologue];
}
_translateEntry(
entry.body, receiverType, keyType, valueType, result, statements);
Statement loopBody =
statements.length == 1 ? statements.first : _createBlock(statements);
if (entry.problem != null) {
body.add(_createExpressionStatement(entry.problem!));
}
ForInStatement loop = _createForInStatement(
entry.fileOffset, entry.variable, entry.iterable, loopBody,
isAsync: entry.isAsync);
libraryBuilder.loader.dataForTesting?.registerAlias(entry, loop);
body.add(loop);
}
void _translateSpreadEntry(
SpreadMapEntry entry,
InterfaceType receiverType,
DartType keyType,
DartType valueType,
VariableDeclaration result,
List<Statement> body) {
Expression value = entry.expression;
final InterfaceType entryType = new InterfaceType(engine.mapEntryClass,
libraryBuilder.nonNullable, <DartType>[keyType, valueType]);
final bool typeMatches = entry.entryType != null &&
typeSchemaEnvironment.isSubtypeOf(
entry.entryType!, entryType, SubtypeCheckMode.withNullabilities);
if (typeMatches) {
// If the type guarantees that all elements are of the required type, use
// a single 'addAll' call instead of a for-loop with calls to '[]='.
// Null-aware spreads require testing the subexpression's value.
VariableDeclaration? temp;
if (entry.isNullAware) {
temp = _createVariable(
value,
typeSchemaEnvironment.mapType(
keyType, valueType, libraryBuilder.nullable));
body.add(temp);
value = _createNullCheckedVariableGet(temp);
}
Statement statement = _createExpressionStatement(_createMapAddAll(
// Don't make a mess of jumping around (and make scope building
// impossible).
_createVariableGet(result)..fileOffset = TreeNode.noOffset,
receiverType,
value));
if (entry.isNullAware) {
statement = _createIf(
temp!.fileOffset,
_createEqualsNull(_createVariableGet(temp), notEquals: true),
statement);
}
body.add(statement);
} else {
// Null-aware spreads require testing the subexpression's value.
VariableDeclaration? temp;
if (entry.isNullAware) {
temp = _createVariable(
value,
typeSchemaEnvironment.mapType(const DynamicType(),
const DynamicType(), libraryBuilder.nullable));
body.add(temp);
value = _createNullCheckedVariableGet(temp);
}
final InterfaceType variableType = new InterfaceType(
engine.mapEntryClass,
libraryBuilder.nonNullable,
<DartType>[const DynamicType(), const DynamicType()]);
VariableDeclaration variable =
_createForInVariable(entry.fileOffset, variableType);
VariableDeclaration keyVar = _createVariable(
_createImplicitAs(
entry.expression.fileOffset,
_createGetKey(entry.expression.fileOffset,
_createVariableGet(variable), variableType),
keyType),
keyType);
VariableDeclaration valueVar = _createVariable(
_createImplicitAs(
entry.expression.fileOffset,
_createGetValue(entry.expression.fileOffset,
_createVariableGet(variable), variableType),
valueType),
valueType);
Statement loopBody = _createBlock(<Statement>[
keyVar,
valueVar,
_createExpressionStatement(_createIndexSet(
entry.expression.fileOffset,
_createVariableGet(result),
receiverType,
_createVariableGet(keyVar),
_createVariableGet(valueVar)))
]);
Statement statement = _createForInStatement(entry.fileOffset, variable,
_createGetEntries(entry.fileOffset, value, receiverType), loopBody);
if (entry.isNullAware) {
statement = _createIf(
temp!.fileOffset,
_createEqualsNull(_createVariableGet(temp), notEquals: true),
statement);
}
body.add(statement);
}
}
Expression _translateConstListOrSet(
Expression node, DartType elementType, List<Expression> elements,
{bool isSet = false}) {
assert((node is ListLiteral && node.isConst) ||
(node is SetLiteral && node.isConst));
// Translate elements in place up to the first non-expression, if any.
int i = 0;
for (; i < elements.length; ++i) {
if (elements[i] is ControlFlowElement) break;
}
// If there were only expressions, we are done.
if (i == elements.length) {
return node;
}
Expression makeLiteral(int fileOffset, List<Expression> expressions) {
if (isSet) {
return _translateConstListOrSet(
_createSetLiteral(fileOffset, elementType, expressions,
isConst: true),
elementType,
expressions,
isSet: true);
} else {
return _translateConstListOrSet(
_createListLiteral(fileOffset, elementType, expressions,
isConst: true),
elementType,
expressions,
isSet: false);
}
}
// Build a concatenation node.
List<Expression> parts = [];
List<Expression>? currentPart = i > 0 ? elements.sublist(0, i) : null;
DartType iterableType = typeSchemaEnvironment.iterableType(
elementType, libraryBuilder.nonNullable);
for (; i < elements.length; ++i) {
Expression element = elements[i];
if (element is SpreadElement) {
if (currentPart != null) {
parts.add(makeLiteral(node.fileOffset, currentPart));
currentPart = null;
}
Expression spreadExpression = element.expression;
if (element.isNullAware) {
VariableDeclaration temp = _createVariable(
spreadExpression,
typeSchemaEnvironment.iterableType(
elementType, libraryBuilder.nullable));
parts.add(_createNullAwareGuard(element.fileOffset, temp,
makeLiteral(element.fileOffset, []), iterableType));
} else {
parts.add(spreadExpression);
}
} else if (element is IfElement) {
if (currentPart != null) {
parts.add(makeLiteral(node.fileOffset, currentPart));
currentPart = null;
}
Expression condition = element.condition;
Expression then = makeLiteral(element.then.fileOffset, [element.then]);
Expression otherwise = element.otherwise != null
? makeLiteral(element.otherwise!.fileOffset, [element.otherwise!])
: makeLiteral(element.fileOffset, []);
parts.add(_createConditionalExpression(
element.fileOffset, condition, then, otherwise, iterableType));
} else if (element is ForElement || element is ForInElement) {
// Rejected earlier.
problems.unhandled("${element.runtimeType}", "_translateConstListOrSet",
element.fileOffset, helper.uri);
} else {
currentPart ??= <Expression>[];
currentPart.add(element);
}
}
if (currentPart != null) {
parts.add(makeLiteral(node.fileOffset, currentPart));
}
if (isSet) {
return new SetConcatenation(parts, typeArgument: elementType)
..fileOffset = node.fileOffset;
} else {
return new ListConcatenation(parts, typeArgument: elementType)
..fileOffset = node.fileOffset;
}
}
Expression _translateConstMap(MapLiteral node) {
assert(node.isConst);
// Translate entries in place up to the first control-flow entry, if any.
int i = 0;
for (; i < node.entries.length; ++i) {
if (node.entries[i] is ControlFlowMapEntry) break;
}
// If there were no control-flow entries we are done.
if (i == node.entries.length) return node;
Expression makeLiteral(int fileOffset, List<MapLiteralEntry> entries) {
return _translateConstMap(_createMapLiteral(
fileOffset, node.keyType, node.valueType, entries,
isConst: true));
}
// Build a concatenation node.
List<Expression> parts = [];
List<MapLiteralEntry>? currentPart =
i > 0 ? node.entries.sublist(0, i) : null;
DartType collectionType = typeSchemaEnvironment.mapType(
node.keyType, node.valueType, libraryBuilder.nonNullable);
for (; i < node.entries.length; ++i) {
MapLiteralEntry entry = node.entries[i];
if (entry is SpreadMapEntry) {
if (currentPart != null) {
parts.add(makeLiteral(node.fileOffset, currentPart));
currentPart = null;
}
Expression spreadExpression = entry.expression;
if (entry.isNullAware) {
VariableDeclaration temp = _createVariable(spreadExpression,
collectionType.withDeclaredNullability(libraryBuilder.nullable));
parts.add(_createNullAwareGuard(entry.fileOffset, temp,
makeLiteral(entry.fileOffset, []), collectionType));
} else {
parts.add(spreadExpression);
}
} else if (entry is IfMapEntry) {
if (currentPart != null) {
parts.add(makeLiteral(node.fileOffset, currentPart));
currentPart = null;
}
Expression condition = entry.condition;
Expression then = makeLiteral(entry.then.fileOffset, [entry.then]);
Expression otherwise = entry.otherwise != null
? makeLiteral(entry.otherwise!.fileOffset, [entry.otherwise!])
: makeLiteral(node.fileOffset, []);
parts.add(_createConditionalExpression(
entry.fileOffset, condition, then, otherwise, collectionType));
} else if (entry is ForMapEntry || entry is ForInMapEntry) {
// Rejected earlier.
problems.unhandled("${entry.runtimeType}", "_translateConstMap",
entry.fileOffset, helper.uri);
} else {
currentPart ??= <MapLiteralEntry>[];
currentPart.add(entry);
}
}
if (currentPart != null) {
parts.add(makeLiteral(node.fileOffset, currentPart));
}
return new MapConcatenation(parts,
keyType: node.keyType, valueType: node.valueType);
}
VariableDeclaration _createVariable(Expression expression, DartType type) {
assert(expression.fileOffset != TreeNode.noOffset);
return new VariableDeclaration.forValue(expression, type: type)
..fileOffset = expression.fileOffset;
}
VariableDeclaration _createForInVariable(int fileOffset, DartType type) {
assert(fileOffset != TreeNode.noOffset);
return new VariableDeclaration.forValue(null, type: type)
..fileOffset = fileOffset;
}
VariableGet _createVariableGet(VariableDeclaration variable) {
assert(variable.fileOffset != TreeNode.noOffset);
return new VariableGet(variable)..fileOffset = variable.fileOffset;
}
VariableGet _createNullCheckedVariableGet(VariableDeclaration variable) {
assert(variable.fileOffset != TreeNode.noOffset);
DartType promotedType =
variable.type.withDeclaredNullability(libraryBuilder.nonNullable);
if (promotedType != variable.type) {
return new VariableGet(variable, promotedType)
..fileOffset = variable.fileOffset;
}
return _createVariableGet(variable);
}
MapLiteral _createMapLiteral(int fileOffset, DartType keyType,
DartType valueType, List<MapLiteralEntry> entries,
{bool isConst = false}) {
assert(fileOffset != TreeNode.noOffset);
return new MapLiteral(entries,
keyType: keyType, valueType: valueType, isConst: isConst)
..fileOffset = fileOffset;
}
ListLiteral _createListLiteral(
int fileOffset, DartType elementType, List<Expression> elements,
{bool isConst = false}) {
assert(fileOffset != TreeNode.noOffset);
return new ListLiteral(elements,
typeArgument: elementType, isConst: isConst)
..fileOffset = fileOffset;
}
SetLiteral _createSetLiteral(
int fileOffset, DartType elementType, List<Expression> elements,
{bool isConst = false}) {
assert(fileOffset != TreeNode.noOffset);
return new SetLiteral(elements, typeArgument: elementType, isConst: isConst)
..fileOffset = fileOffset;
}
Expression _createAdd(
Expression receiver, InterfaceType receiverType, Expression argument,
{required bool isSet}) {
assert(argument.fileOffset != TreeNode.noOffset,
"No fileOffset on ${argument}.");
DartType functionType = Substitution.fromInterfaceType(receiverType)
.substituteType(
isSet ? engine.setAddFunctionType : engine.listAddFunctionType);
if (!isNonNullableByDefault) {
functionType = legacyErasure(functionType);
}
return new InstanceInvocation(InstanceAccessKind.Instance, receiver,
new Name('add'), new Arguments([argument]),
functionType: functionType as FunctionType,
interfaceTarget: isSet ? engine.setAdd : engine.listAdd)
..fileOffset = argument.fileOffset
..isInvariant = true;
}
Expression _createAddAll(Expression receiver, InterfaceType receiverType,
Expression argument, bool isSet) {
assert(argument.fileOffset != TreeNode.noOffset,
"No fileOffset on ${argument}.");
DartType functionType = Substitution.fromInterfaceType(receiverType)
.substituteType(isSet
? engine.setAddAllFunctionType
: engine.listAddAllFunctionType);
if (!isNonNullableByDefault) {
functionType = legacyErasure(functionType);
}
return new InstanceInvocation(InstanceAccessKind.Instance, receiver,
new Name('addAll'), new Arguments([argument]),
functionType: functionType as FunctionType,
interfaceTarget: isSet ? engine.setAddAll : engine.listAddAll)
..fileOffset = argument.fileOffset
..isInvariant = true;
}
Expression _createMapAddAll(
Expression receiver, InterfaceType receiverType, Expression argument) {
assert(argument.fileOffset != TreeNode.noOffset,
"No fileOffset on ${argument}.");
DartType functionType = Substitution.fromInterfaceType(receiverType)
.substituteType(engine.mapAddAllFunctionType);
if (!isNonNullableByDefault) {
functionType = legacyErasure(functionType);
}
return new InstanceInvocation(InstanceAccessKind.Instance, receiver,
new Name('addAll'), new Arguments([argument]),
functionType: functionType as FunctionType,
interfaceTarget: engine.mapAddAll)
..fileOffset = argument.fileOffset
..isInvariant = true;
}
Expression _createEqualsNull(Expression expression,
{bool notEquals = false}) {
assert(expression.fileOffset != TreeNode.noOffset);
Expression check = new EqualsNull(expression)
..fileOffset = expression.fileOffset;
if (notEquals) {
check = new Not(check)..fileOffset = expression.fileOffset;
}
return check;
}
Expression _createIndexSet(int fileOffset, Expression receiver,
InterfaceType receiverType, Expression key, Expression value) {
assert(fileOffset != TreeNode.noOffset);
DartType functionType = Substitution.fromInterfaceType(receiverType)
.substituteType(engine.mapPutFunctionType);
if (!isNonNullableByDefault) {
functionType = legacyErasure(functionType);
}
return new InstanceInvocation(InstanceAccessKind.Instance, receiver,
new Name('[]='), new Arguments([key, value]),
functionType: functionType as FunctionType,
interfaceTarget: engine.mapPut)
..fileOffset = fileOffset
..isInvariant = true;
}
AsExpression _createImplicitAs(
int fileOffset, Expression expression, DartType type) {
assert(fileOffset != TreeNode.noOffset);
return new AsExpression(expression, type)
..isTypeError = true
..isForNonNullableByDefault = isNonNullableByDefault
..fileOffset = fileOffset;
}
IfStatement _createIf(int fileOffset, Expression condition, Statement then,
[Statement? otherwise]) {
assert(fileOffset != TreeNode.noOffset);
return new IfStatement(condition, then, otherwise)..fileOffset = fileOffset;
}
IfCaseStatement _createIfCase(int fileOffset, Expression condition,
DartType matchedValueType, PatternGuard patternGuard, Statement then,
[Statement? otherwise]) {
assert(fileOffset != TreeNode.noOffset);
return new IfCaseStatement(condition, patternGuard, then, otherwise)
..matchedValueType = matchedValueType
..fileOffset = fileOffset;
}
Expression _createGetKey(
int fileOffset, Expression receiver, InterfaceType entryType) {
assert(fileOffset != TreeNode.noOffset);
DartType resultType = Substitution.fromInterfaceType(entryType)
.substituteType(engine.mapEntryKey.type);
return new InstanceGet(
InstanceAccessKind.Instance, receiver, new Name('key'),
interfaceTarget: engine.mapEntryKey, resultType: resultType)
..fileOffset = fileOffset;
}
Expression _createGetValue(
int fileOffset, Expression receiver, InterfaceType entryType) {
assert(fileOffset != TreeNode.noOffset);
DartType resultType = Substitution.fromInterfaceType(entryType)
.substituteType(engine.mapEntryValue.type);
return new InstanceGet(
InstanceAccessKind.Instance, receiver, new Name('value'),
interfaceTarget: engine.mapEntryValue, resultType: resultType)
..fileOffset = fileOffset;
}
Expression _createGetEntries(
int fileOffset, Expression receiver, InterfaceType mapType) {
assert(fileOffset != TreeNode.noOffset);
DartType resultType = Substitution.fromInterfaceType(mapType)
.substituteType(engine.mapEntries.getterType);
return new InstanceGet(
InstanceAccessKind.Instance, receiver, new Name('entries'),
interfaceTarget: engine.mapEntries, resultType: resultType)
..fileOffset = fileOffset;
}
ForStatement _createForStatement(
int fileOffset,
List<VariableDeclaration> variables,
Expression? condition,
List<Expression> updates,
Statement body) {
assert(fileOffset != TreeNode.noOffset);
return new ForStatement(variables, condition, updates, body)
..fileOffset = fileOffset;
}
ForInStatement _createForInStatement(int fileOffset,
VariableDeclaration variable, Expression iterable, Statement body,
{bool isAsync = false}) {
assert(fileOffset != TreeNode.noOffset);
return new ForInStatement(variable, iterable, body, isAsync: isAsync)
..fileOffset = fileOffset;
}
Let _createNullAwareGuard(int fileOffset, VariableDeclaration variable,
Expression defaultValue, DartType type) {
return new Let(
variable,
_createConditionalExpression(
fileOffset,
_createEqualsNull(_createVariableGet(variable)),
defaultValue,
_createNullCheckedVariableGet(variable),
type))
..fileOffset = fileOffset;
}
ConditionalExpression _createConditionalExpression(
int fileOffset,
Expression condition,
Expression then,
Expression otherwise,
DartType type) {
assert(fileOffset != TreeNode.noOffset);
return new ConditionalExpression(condition, then, otherwise, type)
..fileOffset = fileOffset;
}
// Calculates the key and the value type of a spread map entry of type
// spreadMapEntryType and stores them in output in positions offset and offset
// + 1. If the types can't be calculated, for example, if spreadMapEntryType
// is a function type, the original values in output are preserved.
void storeSpreadMapEntryElementTypes(DartType spreadMapEntryType,
bool isNullAware, List<DartType?> output, int offset) {
DartType typeBound = spreadMapEntryType.nonTypeVariableBound;
if (coreTypes.isNull(typeBound)) {
if (isNullAware) {
if (isNonNullableByDefault) {
output[offset] = output[offset + 1] = const NeverType.nonNullable();
} else {
output[offset] = output[offset + 1] = const NullType();
}
}
} else if (typeBound is TypeDeclarationType) {
List<DartType>? supertypeArguments = typeSchemaEnvironment
.getTypeArgumentsAsInstanceOf(typeBound, coreTypes.mapClass);
if (supertypeArguments != null) {
output[offset] = supertypeArguments[0];
output[offset + 1] = supertypeArguments[1];
}
} else if (spreadMapEntryType is DynamicType) {
output[offset] = output[offset + 1] = const DynamicType();
} else if (coreTypes.isBottom(spreadMapEntryType)) {
output[offset] = output[offset + 1] = const NeverType.nonNullable();
}
}
MapLiteralEntry _inferSpreadMapEntry(
SpreadMapEntry entry,
TreeNode parent,
DartType inferredKeyType,
DartType inferredValueType,
DartType spreadContext,
List<DartType> actualTypes,
List<DartType> actualTypesForSet,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes,
_MapLiteralEntryOffsets offsets) {
if (entry.isNullAware) {
spreadContext = computeNullable(spreadContext);
}
ExpressionInferenceResult spreadResult =
inferExpression(entry.expression, spreadContext, isVoidAllowed: true);
entry.expression = spreadResult.expression..parent = entry;
DartType spreadType = spreadResult.inferredType;
inferredSpreadTypes[entry.expression] = spreadType;
int length = actualTypes.length;
actualTypes.add(noInferredType);
actualTypes.add(noInferredType);
storeSpreadMapEntryElementTypes(
spreadType, entry.isNullAware, actualTypes, length);
DartType? actualKeyType = actualTypes[length];
DartType? actualValueType = actualTypes[length + 1];
DartType spreadTypeBound = spreadType.nonTypeVariableBound;
DartType? actualElementType =
getSpreadElementType(spreadType, spreadTypeBound, entry.isNullAware);
MapLiteralEntry replacement = entry;
if (actualKeyType == noInferredType) {
if (coreTypes.isNull(spreadTypeBound) && !entry.isNullAware) {
replacement = new MapLiteralEntry(
helper.buildProblem(
templateNonNullAwareSpreadIsNull.withArguments(
spreadType, isNonNullableByDefault),
entry.expression.fileOffset,
1),
new NullLiteral())
..fileOffset = entry.fileOffset;
} else if (actualElementType != null) {
if (isNonNullableByDefault &&
spreadType.isPotentiallyNullable &&
spreadType is! DynamicType &&
spreadType is! NullType &&
!entry.isNullAware) {
Expression receiver = entry.expression;
Expression problem = helper.buildProblem(
messageNullableSpreadError, receiver.fileOffset, 1,
context: getWhyNotPromotedContext(
flowAnalysis.whyNotPromoted(receiver)(),
entry,
(type) => !type.isPotentiallyNullable));
_copyNonPromotionReasonToReplacement(entry, problem);
replacement = new SpreadMapEntry(problem, isNullAware: false)
..fileOffset = entry.fileOffset;
}
// Don't report the error here, it might be an ambiguous Set. The
// error is reported in checkMapEntry if it's disambiguated as map.
offsets.iterableSpreadType = spreadType;
} else {
Expression receiver = entry.expression;
Expression problem = helper.buildProblem(
templateSpreadMapEntryTypeMismatch.withArguments(
spreadType, isNonNullableByDefault),
receiver.fileOffset,
1,
context: getWhyNotPromotedContext(
flowAnalysis.whyNotPromoted(receiver)(),
entry,
(type) => !type.isPotentiallyNullable));
_copyNonPromotionReasonToReplacement(entry, problem);
replacement = new MapLiteralEntry(problem, new NullLiteral())
..fileOffset = entry.fileOffset;
}
} else if (spreadTypeBound is InterfaceType) {
Expression? keyError;
Expression? valueError;
if (!isAssignable(inferredKeyType, actualKeyType)) {
if (isNonNullableByDefault) {
IsSubtypeOf subtypeCheckResult =
typeSchemaEnvironment.performNullabilityAwareSubtypeCheck(
actualKeyType, inferredKeyType);
if (subtypeCheckResult.isSubtypeWhenIgnoringNullabilities()) {
if (actualKeyType == subtypeCheckResult.subtype &&
inferredKeyType == subtypeCheckResult.supertype) {
keyError = helper.buildProblem(
templateSpreadMapEntryElementKeyTypeMismatchNullability
.withArguments(actualKeyType, inferredKeyType,
isNonNullableByDefault),
entry.expression.fileOffset,
1);
} else {
keyError = helper.buildProblem(
// ignore: lines_longer_than_80_chars
templateSpreadMapEntryElementKeyTypeMismatchPartNullability
.withArguments(
actualKeyType,
inferredKeyType,
subtypeCheckResult.subtype!,
subtypeCheckResult.supertype!,
isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
} else {
keyError = helper.buildProblem(
templateSpreadMapEntryElementKeyTypeMismatch.withArguments(
actualKeyType, inferredKeyType, isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
} else {
keyError = helper.buildProblem(
templateSpreadMapEntryElementKeyTypeMismatch.withArguments(
actualKeyType, inferredKeyType, isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
}
if (!isAssignable(inferredValueType, actualValueType)) {
if (isNonNullableByDefault) {
IsSubtypeOf subtypeCheckResult =
typeSchemaEnvironment.performNullabilityAwareSubtypeCheck(
actualValueType, inferredValueType);
if (subtypeCheckResult.isSubtypeWhenIgnoringNullabilities()) {
if (actualValueType == subtypeCheckResult.subtype &&
inferredValueType == subtypeCheckResult.supertype) {
valueError = helper.buildProblem(
templateSpreadMapEntryElementValueTypeMismatchNullability
.withArguments(actualValueType, inferredValueType,
isNonNullableByDefault),
entry.expression.fileOffset,
1);
} else {
valueError = helper.buildProblem(
// ignore: lines_longer_than_80_chars
templateSpreadMapEntryElementValueTypeMismatchPartNullability
.withArguments(
actualValueType,
inferredValueType,
subtypeCheckResult.subtype!,
subtypeCheckResult.supertype!,
isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
} else {
valueError = helper.buildProblem(
templateSpreadMapEntryElementValueTypeMismatch.withArguments(
actualValueType, inferredValueType, isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
} else {
valueError = helper.buildProblem(
templateSpreadMapEntryElementValueTypeMismatch.withArguments(
actualValueType, inferredValueType, isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
}
if (isNonNullableByDefault &&
spreadType.isPotentiallyNullable &&
spreadType is! DynamicType &&
spreadType is! NullType &&
!entry.isNullAware) {
Expression receiver = entry.expression;
keyError = helper.buildProblem(
messageNullableSpreadError, receiver.fileOffset, 1,
context: getWhyNotPromotedContext(
flowAnalysis.whyNotPromoted(receiver)(),
entry,
(type) => !type.isPotentiallyNullable));
_copyNonPromotionReasonToReplacement(entry, keyError);
}
if (keyError != null || valueError != null) {
keyError ??= new NullLiteral();
valueError ??= new NullLiteral();
replacement = new MapLiteralEntry(keyError, valueError)
..fileOffset = entry.fileOffset;
}
}
// Use 'dynamic' for error recovery.
if (actualKeyType == noInferredType) {
actualKeyType = actualTypes[length] = const DynamicType();
actualValueType = actualTypes[length + 1] = const DynamicType();
}
// Store the type in case of an ambiguous Set. Use 'dynamic' for error
// recovery.
actualTypesForSet.add(actualElementType ?? const DynamicType());
mapEntryClass ??= coreTypes.index.getClass('dart:core', 'MapEntry');
// TODO(cstefantsova): Handle the case of an ambiguous Set.
entry.entryType = new InterfaceType(mapEntryClass!,
libraryBuilder.nonNullable, <DartType>[actualKeyType, actualValueType]);
bool isMap = typeSchemaEnvironment.isSubtypeOf(
spreadType,
coreTypes.mapRawType(libraryBuilder.nullable),
SubtypeCheckMode.withNullabilities);
bool isIterable = typeSchemaEnvironment.isSubtypeOf(
spreadType,
coreTypes.iterableRawType(libraryBuilder.nullable),
SubtypeCheckMode.withNullabilities);
if (isMap && !isIterable) {
offsets.mapSpreadOffset = entry.fileOffset;
}
if (!isMap && isIterable) {
offsets.iterableSpreadOffset = entry.expression.fileOffset;
}
return replacement;
}
MapLiteralEntry _inferIfMapEntry(
IfMapEntry entry,
TreeNode parent,
DartType inferredKeyType,
DartType inferredValueType,
DartType spreadContext,
List<DartType> actualTypes,
List<DartType> actualTypesForSet,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes,
_MapLiteralEntryOffsets offsets) {
flowAnalysis.ifStatement_conditionBegin();
DartType boolType = coreTypes.boolRawType(libraryBuilder.nonNullable);
ExpressionInferenceResult conditionResult =
inferExpression(entry.condition, boolType, isVoidAllowed: false);
Expression condition =
ensureAssignableResult(boolType, conditionResult).expression;
entry.condition = condition..parent = entry;
flowAnalysis.ifStatement_thenBegin(condition, entry);
// Note that this recursive invocation of inferMapEntry will add two types
// to actualTypes; they are the actual types of the current invocation if
// the 'else' branch is empty.
MapLiteralEntry then = inferMapEntry(
entry.then,
entry,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
entry.then = then..parent = entry;
if (entry.otherwise != null) {
flowAnalysis.ifStatement_elseBegin();
// We need to modify the actual types added in the recursive call to
// inferMapEntry.
DartType? actualValueType = actualTypes.removeLast();
DartType? actualKeyType = actualTypes.removeLast();
DartType actualTypeForSet = actualTypesForSet.removeLast();
MapLiteralEntry otherwise = inferMapEntry(
entry.otherwise!,
entry,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
int length = actualTypes.length;
actualTypes[length - 2] = typeSchemaEnvironment.getStandardUpperBound(
actualKeyType, actualTypes[length - 2],
isNonNullableByDefault: isNonNullableByDefault);
actualTypes[length - 1] = typeSchemaEnvironment.getStandardUpperBound(
actualValueType, actualTypes[length - 1],
isNonNullableByDefault: isNonNullableByDefault);
int lengthForSet = actualTypesForSet.length;
actualTypesForSet[lengthForSet - 1] =
typeSchemaEnvironment.getStandardUpperBound(
actualTypeForSet, actualTypesForSet[lengthForSet - 1],
isNonNullableByDefault: isNonNullableByDefault);
entry.otherwise = otherwise..parent = entry;
}
flowAnalysis.ifStatement_end(entry.otherwise != null);
return entry;
}
MapLiteralEntry _inferIfCaseMapEntry(
IfCaseMapEntry entry,
TreeNode parent,
DartType inferredKeyType,
DartType inferredValueType,
DartType spreadContext,
List<DartType> actualTypes,
List<DartType> actualTypesForSet,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes,
_MapLiteralEntryOffsets offsets) {
int? stackBase;
assert(checkStackBase(entry, stackBase = stackHeight));
MapEntryInferenceContext context = new MapEntryInferenceContext(
inferredKeyType: inferredKeyType,
inferredValueType: inferredValueType,
spreadContext: spreadContext,
actualTypes: actualTypes,
actualTypesForSet: actualTypesForSet,
offsets: offsets,
inferredSpreadTypes: inferredSpreadTypes,
inferredConditionTypes: inferredConditionTypes);
IfCaseStatementResult<DartType, InvalidExpression> analysisResult =
analyzeIfCaseElement(
node: entry,
expression: entry.expression,
pattern: entry.patternGuard.pattern,
variables: {
for (VariableDeclaration variable
in entry.patternGuard.pattern.declaredVariables)
variable.name!: variable
},
guard: entry.patternGuard.guard,
ifTrue: entry.then,
ifFalse: entry.otherwise,
context: context);
if (entry.otherwise != null) {
DartType actualValueType = actualTypes.removeLast();
DartType actualKeyType = actualTypes.removeLast();
int length = actualTypes.length;
actualTypes[length - 2] = typeSchemaEnvironment.getStandardUpperBound(
actualKeyType, actualTypes[length - 2],
isNonNullableByDefault: isNonNullableByDefault);
actualTypes[length - 1] = typeSchemaEnvironment.getStandardUpperBound(
actualValueType, actualTypes[length - 1],
isNonNullableByDefault: isNonNullableByDefault);
DartType actualTypeForSet = actualTypesForSet.removeLast();
int lengthForSet = actualTypesForSet.length;
actualTypesForSet[lengthForSet - 1] =
typeSchemaEnvironment.getStandardUpperBound(
actualTypeForSet, actualTypesForSet[lengthForSet - 1],
isNonNullableByDefault: isNonNullableByDefault);
}
entry.matchedValueType = analysisResult.matchedExpressionType;
assert(checkStack(entry, stackBase, [
/* ifFalse = */ unionOfKinds(
[ValueKinds.MapLiteralEntryOrNull, ValueKinds.ExpressionOrNull]),
/* ifTrue = */ unionOfKinds(
[ValueKinds.MapLiteralEntry, ValueKinds.Expression]),
/* guard = */ ValueKinds.ExpressionOrNull,
/* pattern = */ ValueKinds.Pattern,
/* scrutinee = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite(NullValues.Expression);
if (!identical(entry.otherwise, rewrite)) {
entry.otherwise = (rewrite as MapLiteralEntry?)?..parent = entry;
}
rewrite = popRewrite();
if (!identical(entry.then, rewrite)) {
entry.then = (rewrite as MapLiteralEntry)..parent = entry;
}
PatternGuard patternGuard = entry.patternGuard;
rewrite = popRewrite(NullValues.Expression);
InvalidExpression? guardError = analysisResult.nonBooleanGuardError;
if (guardError != null) {
patternGuard.guard = guardError..parent = patternGuard;
} else {
if (!identical(patternGuard.guard, rewrite)) {
patternGuard.guard = (rewrite as Expression?)?..parent = patternGuard;
}
if (analysisResult.guardType is DynamicType) {
patternGuard.guard = _createImplicitAs(patternGuard.guard!.fileOffset,
patternGuard.guard!, coreTypes.boolNonNullableRawType)
..parent = patternGuard;
}
}
rewrite = popRewrite();
if (!identical(patternGuard.pattern, rewrite)) {
patternGuard.pattern = (rewrite as Pattern)..parent = patternGuard;
}
rewrite = popRewrite();
if (!identical(entry.expression, rewrite)) {
entry.expression = (rewrite as Expression)..parent = patternGuard;
}
return entry;
}
MapLiteralEntry _inferForMapEntry(
ForMapEntry entry,
TreeNode parent,
DartType inferredKeyType,
DartType inferredValueType,
DartType spreadContext,
List<DartType> actualTypes,
List<DartType> actualTypesForSet,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes,
_MapLiteralEntryOffsets offsets) {
if (entry is PatternForMapEntry) {
int? stackBase;
assert(checkStackBase(entry, stackBase = stackHeight));
PatternVariableDeclaration patternVariableDeclaration =
entry.patternVariableDeclaration;
PatternVariableDeclarationAnalysisResult<DartType, DartType>
analysisResult = analyzePatternVariableDeclaration(
patternVariableDeclaration,
patternVariableDeclaration.pattern,
patternVariableDeclaration.initializer,
isFinal: patternVariableDeclaration.isFinal);
patternVariableDeclaration.matchedValueType =
analysisResult.initializerType;
assert(checkStack(entry, stackBase, [
/* pattern = */ ValueKinds.Pattern,
/* initializer = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite(NullValues.Expression);
if (!identical(patternVariableDeclaration.pattern, rewrite)) {
patternVariableDeclaration.pattern = (rewrite as Pattern)
..parent = patternVariableDeclaration;
}
rewrite = popRewrite();
if (!identical(patternVariableDeclaration.initializer, rewrite)) {
patternVariableDeclaration.initializer = (rewrite as Expression)
..parent = patternVariableDeclaration;
}
List<VariableDeclaration> declaredVariables =
patternVariableDeclaration.pattern.declaredVariables;
assert(declaredVariables.length == entry.intermediateVariables.length);
assert(declaredVariables.length == entry.variables.length);
for (int i = 0; i < declaredVariables.length; i++) {
DartType type = declaredVariables[i].type;
entry.intermediateVariables[i].type = type;
entry.variables[i].type = type;
}
}
// TODO(johnniwinther): Use _visitStatements instead.
List<VariableDeclaration>? variables;
for (int index = 0; index < entry.variables.length; index++) {
VariableDeclaration variable = entry.variables[index];
if (variable.name == null) {
if (variable.initializer != null) {
ExpressionInferenceResult result = inferExpression(
variable.initializer!, variable.type,
isVoidAllowed: true);
variable.initializer = result.expression..parent = variable;
variable.type = result.inferredType;
}
} else {
StatementInferenceResult variableResult = inferStatement(variable);
if (variableResult.hasChanged) {
if (variables == null) {
variables = <VariableDeclaration>[];
variables.addAll(entry.variables.sublist(0, index));
}
if (variableResult.statementCount == 1) {
variables.add(variableResult.statement as VariableDeclaration);
} else {
for (Statement variable in variableResult.statements) {
variables.add(variable as VariableDeclaration);
}
}
} else if (variables != null) {
variables.add(variable);
}
}
}
if (variables != null) {
entry.variables.clear();
entry.variables.addAll(variables);
setParents(variables, entry);
}
flowAnalysis.for_conditionBegin(entry);
if (entry.condition != null) {
ExpressionInferenceResult conditionResult = inferExpression(
entry.condition!, coreTypes.boolRawType(libraryBuilder.nonNullable),
isVoidAllowed: false);
entry.condition = conditionResult.expression..parent = entry;
// TODO(johnniwinther): Ensure assignability of condition?
inferredConditionTypes[entry.condition!] = conditionResult.inferredType;
}
flowAnalysis.for_bodyBegin(null, entry.condition);
// Actual types are added by the recursive call.
MapLiteralEntry body = inferMapEntry(
entry.body,
entry,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
entry.body = body..parent = entry;
flowAnalysis.for_updaterBegin();
for (int index = 0; index < entry.updates.length; index++) {
ExpressionInferenceResult updateResult = inferExpression(
entry.updates[index], const UnknownType(),
isVoidAllowed: true);
entry.updates[index] = updateResult.expression..parent = entry;
}
flowAnalysis.for_end();
return entry;
}
MapLiteralEntry _inferForInMapEntry(
ForInMapEntry entry,
TreeNode parent,
DartType inferredKeyType,
DartType inferredValueType,
DartType spreadContext,
List<DartType> actualTypes,
List<DartType> actualTypesForSet,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes,
_MapLiteralEntryOffsets offsets) {
ForInResult result;
if (entry.variable.name == null) {
result = handleForInWithoutVariable(entry, entry.variable, entry.iterable,
entry.syntheticAssignment, entry.expressionEffects,
isAsync: entry.isAsync, hasProblem: entry.problem != null);
} else {
result = handleForInDeclaringVariable(
entry, entry.variable, entry.iterable, entry.expressionEffects,
isAsync: entry.isAsync);
}
entry.variable = result.variable..parent = entry;
entry.iterable = result.iterable..parent = entry;
// TODO(johnniwinther): Use ?.. here instead.
entry.syntheticAssignment = result.syntheticAssignment;
result.syntheticAssignment?.parent = entry;
// TODO(johnniwinther): Use ?.. here instead.
entry.expressionEffects = result.expressionSideEffects;
result.expressionSideEffects?.parent = entry;
if (entry.problem != null) {
ExpressionInferenceResult problemResult = inferExpression(
entry.problem!, const UnknownType(),
isVoidAllowed: true);
entry.problem = problemResult.expression..parent = entry;
}
// Actual types are added by the recursive call.
MapLiteralEntry body = inferMapEntry(
entry.body,
entry,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
entry.body = body..parent = entry;
// This is matched by the call to [forEach_bodyBegin] in
// [handleForInWithoutVariable] or [handleForInDeclaringVariable].
flowAnalysis.forEach_end();
return entry;
}
// Note that inferMapEntry adds exactly two elements to actualTypes -- the
// actual types of the key and the value. The same technique is used for
// actualTypesForSet, only inferMapEntry adds exactly one element to that
// list: the actual type of the iterable spread elements in case the map
// literal will be disambiguated as a set literal later.
MapLiteralEntry inferMapEntry(
MapLiteralEntry entry,
TreeNode parent,
DartType inferredKeyType,
DartType inferredValueType,
DartType spreadContext,
List<DartType> actualTypes,
List<DartType> actualTypesForSet,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes,
_MapLiteralEntryOffsets offsets) {
if (entry is SpreadMapEntry) {
return _inferSpreadMapEntry(
entry,
parent,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
} else if (entry is IfMapEntry) {
return _inferIfMapEntry(
entry,
parent,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
} else if (entry is IfCaseMapEntry) {
return _inferIfCaseMapEntry(
entry,
parent,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
} else if (entry is ForMapEntry) {
return _inferForMapEntry(
entry,
parent,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
} else if (entry is ForInMapEntry) {
return _inferForInMapEntry(
entry,
parent,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
} else {
ExpressionInferenceResult keyResult =
inferExpression(entry.key, inferredKeyType, isVoidAllowed: true);
Expression key = ensureAssignableResult(inferredKeyType, keyResult,
isVoidAllowed: inferredKeyType is VoidType)
.expression;
entry.key = key..parent = entry;
ExpressionInferenceResult valueResult =
inferExpression(entry.value, inferredValueType, isVoidAllowed: true);
Expression value = ensureAssignableResult(inferredValueType, valueResult,
isVoidAllowed: inferredValueType is VoidType)
.expression;
entry.value = value..parent = entry;
actualTypes.add(keyResult.inferredType);
actualTypes.add(valueResult.inferredType);
// Use 'dynamic' for error recovery.
actualTypesForSet.add(const DynamicType());
offsets.mapEntryOffset = entry.fileOffset;
return entry;
}
}
MapLiteralEntry checkMapEntry(
MapLiteralEntry entry,
DartType keyType,
DartType valueType,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes,
_MapLiteralEntryOffsets offsets) {
// It's disambiguated as a map literal.
MapLiteralEntry replacement = entry;
if (offsets.iterableSpreadOffset != null) {
replacement = new MapLiteralEntry(
helper.buildProblem(
templateSpreadMapEntryTypeMismatch.withArguments(
offsets.iterableSpreadType!, isNonNullableByDefault),
offsets.iterableSpreadOffset!,
1),
new NullLiteral())
..fileOffset = offsets.iterableSpreadOffset!;
}
if (entry is SpreadMapEntry) {
DartType? spreadType = inferredSpreadTypes[entry.expression];
if (spreadType is DynamicType) {
Expression expression = ensureAssignable(
coreTypes
.mapRawType(libraryBuilder.nullableIfTrue(entry.isNullAware)),
spreadType,
entry.expression);
entry.expression = expression..parent = entry;
}
} else if (entry is IfMapEntry) {
MapLiteralEntry then = checkMapEntry(entry.then, keyType, valueType,
inferredSpreadTypes, inferredConditionTypes, offsets);
entry.then = then..parent = entry;
if (entry.otherwise != null) {
MapLiteralEntry otherwise = checkMapEntry(entry.otherwise!, keyType,
valueType, inferredSpreadTypes, inferredConditionTypes, offsets);
entry.otherwise = otherwise..parent = entry;
}
} else if (entry is ForMapEntry) {
if (entry.condition != null) {
DartType conditionType = inferredConditionTypes[entry.condition]!;
Expression condition = ensureAssignable(
coreTypes.boolRawType(libraryBuilder.nonNullable),
conditionType,
entry.condition!);
entry.condition = condition..parent = entry;
}
MapLiteralEntry body = checkMapEntry(entry.body, keyType, valueType,
inferredSpreadTypes, inferredConditionTypes, offsets);
entry.body = body..parent = entry;
} else if (entry is ForInMapEntry) {
MapLiteralEntry body = checkMapEntry(entry.body, keyType, valueType,
inferredSpreadTypes, inferredConditionTypes, offsets);
entry.body = body..parent = entry;
} else {
// Do nothing. Assignability checks are done during type inference.
}
return replacement;
}
@override
ExpressionInferenceResult visitMapLiteral(
MapLiteral node, DartType typeContext) {
Class mapClass = coreTypes.mapClass;
InterfaceType mapType =
coreTypes.thisInterfaceType(mapClass, libraryBuilder.nonNullable);
List<DartType>? inferredTypes;
DartType inferredKeyType;
DartType inferredValueType;
assert((node.keyType is ImplicitTypeArgument) ==
(node.valueType is ImplicitTypeArgument));
bool inferenceNeeded = node.keyType is ImplicitTypeArgument;
bool typeContextIsMap = node.keyType is! ImplicitTypeArgument;
DartType? typeContextAsIterable;
DartType? unfuturedTypeContext = typeSchemaEnvironment.flatten(typeContext);
// Ambiguous set/map literal
if (unfuturedTypeContext is TypeDeclarationType) {
if (!typeContextIsMap) {
// TODO(johnniwinther): Can we use the found type arguments instead of
// the inferred types?
typeContextIsMap = hierarchyBuilder.getTypeArgumentsAsInstanceOf(
unfuturedTypeContext, coreTypes.mapClass) !=
null;
}
typeContextAsIterable = hierarchyBuilder.getTypeAsInstanceOf(
unfuturedTypeContext, coreTypes.iterableClass,
isNonNullableByDefault: isNonNullableByDefault);
if (node.entries.isEmpty &&
typeContextAsIterable != null &&
!typeContextIsMap) {
// Set literal
SetLiteral setLiteral = new SetLiteral([],
typeArgument: const ImplicitTypeArgument(), isConst: node.isConst)
..fileOffset = node.fileOffset;
return visitSetLiteral(setLiteral, typeContext);
}
}
List<DartType> formalTypes = [];
List<DartType> actualTypes = [];
List<DartType> actualTypesForSet = [];
Map<TreeNode, DartType> inferredSpreadTypes =
new Map<TreeNode, DartType>.identity();
Map<Expression, DartType> inferredConditionTypes =
new Map<Expression, DartType>.identity();
TypeConstraintGatherer? gatherer;
FreshStructuralParametersFromTypeParameters freshTypeParameters =
getFreshStructuralParametersFromTypeParameters(mapClass.typeParameters);
List<StructuralParameter> typeParametersToInfer =
freshTypeParameters.freshTypeParameters;
mapType = freshTypeParameters.substitute(mapType) as InterfaceType;
if (inferenceNeeded) {
gatherer = typeSchemaEnvironment.setupGenericTypeInference(
mapType, typeParametersToInfer, typeContext,
isNonNullableByDefault: isNonNullableByDefault,
isConst: node.isConst,
typeOperations: operations,
inferenceResultForTesting: dataForTesting?.typeInferenceResult,
treeNodeForTesting: node);
inferredTypes = typeSchemaEnvironment.choosePreliminaryTypes(
gatherer, typeParametersToInfer, null,
isNonNullableByDefault: isNonNullableByDefault);
inferredKeyType = inferredTypes[0];
inferredValueType = inferredTypes[1];
} else {
inferredKeyType = node.keyType;
inferredValueType = node.valueType;
}
bool hasMapEntry = false;
bool hasMapSpread = false;
bool hasIterableSpread = false;
_MapLiteralEntryOffsets offsets = new _MapLiteralEntryOffsets();
DartType spreadTypeContext = const UnknownType();
if (typeContextAsIterable != null && !typeContextIsMap) {
spreadTypeContext = typeContextAsIterable;
} else if (typeContextAsIterable == null && typeContextIsMap) {
spreadTypeContext = new InterfaceType(
coreTypes.mapClass,
libraryBuilder.nonNullable,
<DartType>[inferredKeyType, inferredValueType]);
}
for (int index = 0; index < node.entries.length; ++index) {
MapLiteralEntry entry = inferMapEntry(
node.entries[index],
node,
inferredKeyType,
inferredValueType,
spreadTypeContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
offsets);
node.entries[index] = entry..parent = node;
if (inferenceNeeded) {
formalTypes.add(mapType.typeArguments[0]);
formalTypes.add(mapType.typeArguments[1]);
}
}
hasMapEntry = offsets.mapEntryOffset != null;
hasMapSpread = offsets.mapSpreadOffset != null;
hasIterableSpread = offsets.iterableSpreadOffset != null;
if (inferenceNeeded) {
bool canBeSet = !hasMapSpread && !hasMapEntry && !typeContextIsMap;
bool canBeMap = !hasIterableSpread && typeContextAsIterable == null;
if (canBeSet && !canBeMap) {
List<Expression> setElements = <Expression>[];
List<DartType> formalTypesForSet = <DartType>[];
InterfaceType setType = coreTypes.thisInterfaceType(
coreTypes.setClass, libraryBuilder.nonNullable);
FreshStructuralParametersFromTypeParameters freshTypeParameters =
getFreshStructuralParametersFromTypeParameters(
coreTypes.setClass.typeParameters);
List<StructuralParameter> typeParametersToInfer =
freshTypeParameters.freshTypeParameters;
setType = freshTypeParameters.substitute(setType) as InterfaceType;
for (int i = 0; i < node.entries.length; ++i) {
setElements.add(convertToElement(
node.entries[i],
helper,
assignedVariables.reassignInfo,
actualType: actualTypesForSet[i],
));
formalTypesForSet.add(setType.typeArguments[0]);
}
// Note: we don't use the previously created gatherer because it was set
// up presuming that the literal would be a map; we now know that it
// needs to be a set.
TypeConstraintGatherer gatherer =
typeSchemaEnvironment.setupGenericTypeInference(
setType, typeParametersToInfer, typeContext,
isNonNullableByDefault: isNonNullableByDefault,
isConst: node.isConst,
typeOperations: operations,
inferenceResultForTesting: dataForTesting?.typeInferenceResult,
treeNodeForTesting: node);
List<DartType> inferredTypesForSet = typeSchemaEnvironment
.choosePreliminaryTypes(gatherer, typeParametersToInfer, null,
isNonNullableByDefault: isNonNullableByDefault);
gatherer.constrainArguments(formalTypesForSet, actualTypesForSet,
treeNodeForTesting: node);
inferredTypesForSet = typeSchemaEnvironment.chooseFinalTypes(
gatherer, typeParametersToInfer, inferredTypesForSet,
isNonNullableByDefault: isNonNullableByDefault);
DartType inferredTypeArgument = inferredTypesForSet[0];
instrumentation?.record(
uriForInstrumentation,
node.fileOffset,
'typeArgs',
new InstrumentationValueForTypeArgs([inferredTypeArgument]));
SetLiteral setLiteral = new SetLiteral(setElements,
typeArgument: inferredTypeArgument, isConst: node.isConst)
..fileOffset = node.fileOffset;
for (int i = 0; i < setLiteral.expressions.length; i++) {
checkElement(
setLiteral.expressions[i],
setLiteral,
setLiteral.typeArgument,
inferredSpreadTypes,
inferredConditionTypes);
}
Expression result = _translateSetLiteral(setLiteral);
DartType inferredType = new InterfaceType(coreTypes.setClass,
libraryBuilder.nonNullable, inferredTypesForSet);
return new ExpressionInferenceResult(inferredType, result);
}
if (canBeSet && canBeMap && node.entries.isNotEmpty) {
Expression replacement = helper.buildProblem(
messageCantDisambiguateNotEnoughInformation, node.fileOffset, 1);
return new ExpressionInferenceResult(
NeverType.fromNullability(libraryBuilder.nonNullable), replacement);
}
if (!canBeSet && !canBeMap) {
Expression replacement = helper.buildProblem(
messageCantDisambiguateAmbiguousInformation, node.fileOffset, 1);
return new ExpressionInferenceResult(
NeverType.fromNullability(libraryBuilder.nonNullable), replacement);
}
gatherer!.constrainArguments(formalTypes, actualTypes,
treeNodeForTesting: node);
inferredTypes = typeSchemaEnvironment.chooseFinalTypes(
gatherer, typeParametersToInfer, inferredTypes!,
isNonNullableByDefault: isNonNullableByDefault);
if (dataForTesting != null) {
dataForTesting!.typeInferenceResult.inferredTypeArguments[node] =
inferredTypes;
}
inferredKeyType = inferredTypes[0];
inferredValueType = inferredTypes[1];
instrumentation?.record(
uriForInstrumentation,
node.fileOffset,
'typeArgs',
new InstrumentationValueForTypeArgs(
[inferredKeyType, inferredValueType]));
node.keyType = inferredKeyType;
node.valueType = inferredValueType;
}
for (int index = 0; index < node.entries.length; ++index) {
MapLiteralEntry entry = checkMapEntry(node.entries[index], node.keyType,
node.valueType, inferredSpreadTypes, inferredConditionTypes, offsets);
node.entries[index] = entry..parent = node;
}
DartType inferredType = new InterfaceType(mapClass,
libraryBuilder.nonNullable, [inferredKeyType, inferredValueType]);
SourceLibraryBuilder library = libraryBuilder;
// Either both [_declaredKeyType] and [_declaredValueType] are omitted or
// none of them, so we may just check one.
if (inferenceNeeded) {
if (!library.libraryFeatures.genericMetadata.isEnabled) {
checkGenericFunctionTypeArgument(node.keyType, node.fileOffset);
checkGenericFunctionTypeArgument(node.valueType, node.fileOffset);
}
}
Expression result = _translateMapLiteral(node);
return new ExpressionInferenceResult(inferredType, result);
}
ExpressionInferenceResult visitMethodInvocation(
MethodInvocation node, DartType typeContext) {
assert(node.name != unaryMinusName);
ExpressionInferenceResult result =
inferNullAwareExpression(node.receiver, const UnknownType());
Link<NullAwareGuard> nullAwareGuards = result.nullAwareGuards;
Expression receiver = result.nullAwareAction;
DartType receiverType = result.nullAwareActionType;
return inferMethodInvocation(
this,
node.fileOffset,
nullAwareGuards,
receiver,
receiverType,
node.name,
node.arguments as ArgumentsImpl,
typeContext,
isExpressionInvocation: false,
isImplicitCall: false);
}
ExpressionInferenceResult visitAugmentSuperInvocation(
AugmentSuperInvocation node, DartType typeContext) {
Member member = node.target;
if (member.isInstanceMember) {
ObjectAccessTarget target = new ObjectAccessTarget.interfaceMember(
thisType!, member,
hasNonObjectMemberAccess: true);
Link<NullAwareGuard> nullAwareGuards = const Link<NullAwareGuard>();
Expression receiver = new ThisExpression()..fileOffset = node.fileOffset;
DartType receiverType = thisType!;
return inferMethodInvocation(
this,
node.fileOffset,
nullAwareGuards,
receiver,
receiverType,
member.name,
node.arguments as ArgumentsImpl,
typeContext,
isExpressionInvocation: false,
isImplicitCall: false,
target: target);
} else if (member is Procedure) {
FunctionType calleeType =
member.function.computeFunctionType(libraryBuilder.nonNullable);
TypeArgumentsInfo typeArgumentsInfo =
getTypeArgumentsInfo(node.arguments);
InvocationInferenceResult result = inferInvocation(this, typeContext,
node.fileOffset, calleeType, node.arguments as ArgumentsImpl,
staticTarget: node.target);
StaticInvocation invocation =
new StaticInvocation(member, node.arguments);
libraryBuilder.checkBoundsInStaticInvocation(
invocation, typeSchemaEnvironment, helper.uri, typeArgumentsInfo);
return new ExpressionInferenceResult(
result.inferredType, result.applyResult(invocation));
} else {
// TODO(johnniwinther): Handle augmentation of field with inferred types.
TypeInferenceEngine.resolveInferenceNode(member, hierarchyBuilder);
Link<NullAwareGuard> nullAwareGuards = const Link<NullAwareGuard>();
DartType receiverType = member.getterType;
Expression receiver = new StaticGet(member)..fileOffset = node.fileOffset;
return inferMethodInvocation(
this,
node.fileOffset,
nullAwareGuards,
receiver,
receiverType,
callName,
node.arguments as ArgumentsImpl,
typeContext,
isExpressionInvocation: true,
isImplicitCall: true);
}
}
ExpressionInferenceResult visitExpressionInvocation(
ExpressionInvocation node, DartType typeContext) {
ExpressionInferenceResult result =
inferNullAwareExpression(node.expression, const UnknownType());
Link<NullAwareGuard> nullAwareGuards = result.nullAwareGuards;
Expression receiver = result.nullAwareAction;
DartType receiverType = result.nullAwareActionType;
return inferMethodInvocation(
this,
node.fileOffset,
nullAwareGuards,
receiver,
receiverType,
callName,
node.arguments as ArgumentsImpl,
typeContext,
isExpressionInvocation: true,
isImplicitCall: true);
}
@override
ExpressionInferenceResult visitNot(Not node, DartType typeContext) {
InterfaceType boolType = coreTypes.boolRawType(libraryBuilder.nonNullable);
ExpressionInferenceResult operandResult =
inferExpression(node.operand, boolType);
Expression operand = ensureAssignableResult(boolType, operandResult,
fileOffset: node.fileOffset)
.expression;
node.operand = operand..parent = node;
flowAnalysis.logicalNot_end(node, node.operand);
return new ExpressionInferenceResult(boolType, node);
}
@override
ExpressionInferenceResult visitNullCheck(
NullCheck node, DartType typeContext) {
ExpressionInferenceResult operandResult =
inferNullAwareExpression(node.operand, computeNullable(typeContext));
Link<NullAwareGuard> nullAwareGuards = operandResult.nullAwareGuards;
Expression operand = operandResult.nullAwareAction;
DartType operandType = operandResult.nullAwareActionType;
node.operand = operand..parent = node;
flowAnalysis.nonNullAssert_end(node.operand);
DartType nonNullableResultType = operations.promoteToNonNull(operandType);
return createNullAwareExpressionInferenceResult(
nonNullableResultType, node, nullAwareGuards);
}
ExpressionInferenceResult visitNullAwareMethodInvocation(
NullAwareMethodInvocation node, DartType typeContext) {
Link<NullAwareGuard> nullAwareGuards =
inferSyntheticVariableNullAware(node.variable);
NullAwareGuard nullAwareGuard = createNullAwareGuard(node.variable);
ExpressionInferenceResult invocationResult =
inferExpression(node.invocation, typeContext, isVoidAllowed: true);
return createNullAwareExpressionInferenceResult(
invocationResult.inferredType,
invocationResult.expression,
nullAwareGuards.prepend(nullAwareGuard));
}
ExpressionInferenceResult visitNullAwarePropertyGet(
NullAwarePropertyGet node, DartType typeContext) {
Link<NullAwareGuard> nullAwareGuards =
inferSyntheticVariableNullAware(node.variable);
NullAwareGuard nullAwareGuard = createNullAwareGuard(node.variable);
ExpressionInferenceResult readResult =
inferExpression(node.read, typeContext);
return createNullAwareExpressionInferenceResult(readResult.inferredType,
readResult.expression, nullAwareGuards.prepend(nullAwareGuard));
}
ExpressionInferenceResult visitNullAwarePropertySet(
NullAwarePropertySet node, DartType typeContext) {
Link<NullAwareGuard> nullAwareGuards =
inferSyntheticVariableNullAware(node.variable);
NullAwareGuard nullAwareGuard = createNullAwareGuard(node.variable);
ExpressionInferenceResult writeResult =
inferExpression(node.write, typeContext, isVoidAllowed: true);
return createNullAwareExpressionInferenceResult(writeResult.inferredType,
writeResult.expression, nullAwareGuards.prepend(nullAwareGuard));
}
ExpressionInferenceResult visitNullAwareExtension(
NullAwareExtension node, DartType typeContext) {
inferSyntheticVariable(node.variable);
NullAwareGuard nullAwareGuard = createNullAwareGuard(node.variable);
ExpressionInferenceResult expressionResult =
inferExpression(node.expression, typeContext);
return createNullAwareExpressionInferenceResult(
expressionResult.inferredType,
expressionResult.expression,
const Link<NullAwareGuard>().prepend(nullAwareGuard));
}
ExpressionInferenceResult visitStaticPostIncDec(
StaticPostIncDec node, DartType typeContext) {
inferSyntheticVariable(node.read);
inferSyntheticVariable(node.write);
DartType inferredType = node.read.type;
Expression replacement =
new Let(node.read, createLet(node.write, createVariableGet(node.read)))
..fileOffset = node.fileOffset;
return new ExpressionInferenceResult(inferredType, replacement);
}
ExpressionInferenceResult visitLocalPostIncDec(
LocalPostIncDec node, DartType typeContext) {
inferSyntheticVariable(node.read);
inferSyntheticVariable(node.write);
DartType inferredType = node.read.type;
Expression replacement =
new Let(node.read, createLet(node.write, createVariableGet(node.read)))
..fileOffset = node.fileOffset;
return new ExpressionInferenceResult(inferredType, replacement);
}
ExpressionInferenceResult visitPropertyPostIncDec(
PropertyPostIncDec node, DartType typeContext) {
if (node.variable != null) {
inferSyntheticVariable(node.variable!);
}
inferSyntheticVariable(node.read);
inferSyntheticVariable(node.write);
DartType inferredType = node.read.type;
Expression replacement;
if (node.variable != null) {
replacement = new Let(
node.variable!,
createLet(
node.read, createLet(node.write, createVariableGet(node.read))))
..fileOffset = node.fileOffset;
} else {
replacement = new Let(
node.read, createLet(node.write, createVariableGet(node.read)))
..fileOffset = node.fileOffset;
}
return new ExpressionInferenceResult(inferredType, replacement);
}
ExpressionInferenceResult visitCompoundPropertySet(
CompoundPropertySet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferNullAwareExpression(
node.receiver, const UnknownType(),
isVoidAllowed: false);
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
VariableDeclaration? receiverVariable;
Expression readReceiver;
Expression writeReceiver;
if (isPureExpression(receiver)) {
readReceiver = receiver;
writeReceiver = clonePureExpression(receiver);
} else {
receiverVariable = createVariable(receiver, receiverType);
instrumentation?.record(
uriForInstrumentation,
receiverVariable.fileOffset,
'type',
new InstrumentationValueForType(receiverType));
readReceiver = createVariableGet(receiverVariable);
writeReceiver = createVariableGet(receiverVariable);
}
ExpressionInferenceResult readResult = _computePropertyGet(node.readOffset,
readReceiver, receiverType, node.propertyName, const UnknownType(),
isThisReceiver: node.receiver is ThisExpression)
.expressionInferenceResult;
Expression read = readResult.expression;
DartType readType = readResult.inferredType;
ObjectAccessTarget writeTarget = findInterfaceMember(
receiverType, node.propertyName, node.writeOffset,
isSetter: true, instrumented: true, includeExtensionMethods: true);
DartType writeType = writeTarget.getSetterType(this);
ExpressionInferenceResult binaryResult = _computeBinaryExpression(
node.binaryOffset,
writeType,
read,
readType,
node.binaryName,
node.rhs,
null);
binaryResult = ensureAssignableResult(writeType, binaryResult);
DartType binaryType = binaryResult.inferredType;
Expression binary = binaryResult.expression;
ExpressionInferenceResult writeResult = _computePropertySet(
node.writeOffset,
writeReceiver,
receiverType,
node.propertyName,
writeTarget,
binary,
valueType: binaryType,
forEffect: node.forEffect);
Expression write = writeResult.expression;
Expression replacement = write;
if (receiverVariable != null) {
replacement = createLet(receiverVariable, replacement);
}
replacement.fileOffset = node.fileOffset;
return createNullAwareExpressionInferenceResult(
binaryType, replacement, nullAwareGuards);
}
ExpressionInferenceResult visitIfNullPropertySet(
IfNullPropertySet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferNullAwareExpression(
node.receiver, const UnknownType(),
isVoidAllowed: false);
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
VariableDeclaration receiverVariable =
createVariable(receiver, receiverType);
instrumentation?.record(uriForInstrumentation, receiverVariable.fileOffset,
'type', new InstrumentationValueForType(receiverType));
Expression readReceiver = createVariableGet(receiverVariable);
Expression writeReceiver = createVariableGet(receiverVariable);
ExpressionInferenceResult readResult = _computePropertyGet(node.readOffset,
readReceiver, receiverType, node.propertyName, const UnknownType(),
isThisReceiver: node.receiver is ThisExpression)
.expressionInferenceResult;
Expression read = readResult.expression;
DartType readType = readResult.inferredType;
ObjectAccessTarget writeTarget = findInterfaceMember(
receiverType, node.propertyName, receiver.fileOffset,
isSetter: true, instrumented: true, includeExtensionMethods: true);
DartType writeContext = writeTarget.getSetterType(this);
flowAnalysis.ifNullExpression_rightBegin(read, readType);
ExpressionInferenceResult rhsResult =
inferExpression(node.rhs, writeContext, isVoidAllowed: true);
flowAnalysis.ifNullExpression_end();
rhsResult = ensureAssignableResult(writeContext, rhsResult);
Expression rhs = rhsResult.expression;
DartType writeType = rhsResult.inferredType;
ExpressionInferenceResult writeResult = _computePropertySet(
node.writeOffset,
writeReceiver,
receiverType,
node.propertyName,
writeTarget,
rhs,
forEffect: node.forEffect,
valueType: writeType);
Expression write = writeResult.expression;
DartType nonNullableReadType = readType.toNonNull();
DartType inferredType = _analyzeIfNullTypes(
nonNullableReadType: nonNullableReadType,
rhsType: writeType,
typeContext: typeContext);
Expression replacement;
if (node.forEffect) {
// Encode `o.a ??= b` as:
//
// let v1 = o in v1.a == null ? v1.a = b : null
//
Expression equalsNull =
createEqualsNull(read, fileOffset: node.fileOffset);
ConditionalExpression conditional = new ConditionalExpression(
equalsNull,
write,
new NullLiteral()..fileOffset = node.fileOffset,
computeNullable(inferredType))
..fileOffset = node.fileOffset;
replacement =
new Let(receiverVariable, conditional..fileOffset = node.fileOffset)
..fileOffset = node.fileOffset;
} else {
// Encode `o.a ??= b` as:
//
// let v1 = o in let v2 = v1.a in v2 == null ? v1.a = b : v2
//
VariableDeclaration readVariable = createVariable(read, readType);
Expression equalsNull = createEqualsNull(createVariableGet(readVariable),
fileOffset: node.fileOffset);
VariableGet variableGet = createVariableGet(readVariable);
if (isNonNullableByDefault && !identical(nonNullableReadType, readType)) {
variableGet.promotedType = nonNullableReadType;
}
ConditionalExpression conditional = new ConditionalExpression(
equalsNull, write, variableGet, inferredType)
..fileOffset = node.fileOffset;
replacement =
new Let(receiverVariable, createLet(readVariable, conditional))
..fileOffset = node.fileOffset;
}
return createNullAwareExpressionInferenceResult(
inferredType, replacement, nullAwareGuards);
}
DartType _analyzeIfNullTypes(
{required DartType nonNullableReadType,
required DartType rhsType,
required DartType typeContext}) {
// - 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.
// - Let `T2` be the type of `e` inferred with context type `T1`.
DartType t2 = rhsType;
// - Let `T` be `UP(NonNull(T1), T2)`.
DartType nonNullT1 = nonNullableReadType;
DartType t = typeSchemaEnvironment.getStandardUpperBound(nonNullT1, t2,
isNonNullableByDefault: isNonNullableByDefault);
// - Let `S` be the greatest closure of `K`.
DartType s = computeGreatestClosure(typeContext);
// If `inferenceUpdate3` is not enabled, then the type of `E` is `T`.
if (!libraryBuilder.libraryFeatures.inferenceUpdate3.isEnabled) {
return t;
} else
// - If `T <: S`, then the type of `E` is `T`.
if (typeSchemaEnvironment.isSubtypeOf(
t, s, SubtypeCheckMode.withNullabilities)) {
return t;
}
// - Otherwise, if `NonNull(T1) <: S` and `T2 <: S`, then the type of
// `E` is `S`.
if (typeSchemaEnvironment.isSubtypeOf(
nonNullT1, s, SubtypeCheckMode.withNullabilities) &&
typeSchemaEnvironment.isSubtypeOf(
t2, s, SubtypeCheckMode.withNullabilities)) {
return s;
}
// - Otherwise, the type of `E` is `T`.
return t;
}
ExpressionInferenceResult visitIfNullSet(
IfNullSet node, DartType typeContext) {
ExpressionInferenceResult readResult =
inferNullAwareExpression(node.read, const UnknownType());
Link<NullAwareGuard> nullAwareGuards = readResult.nullAwareGuards;
Expression read = readResult.nullAwareAction;
DartType readType = readResult.nullAwareActionType;
flowAnalysis.ifNullExpression_rightBegin(read, readType);
ExpressionInferenceResult writeResult =
inferExpression(node.write, typeContext, isVoidAllowed: true);
flowAnalysis.ifNullExpression_end();
DartType originalReadType = readType;
DartType nonNullableReadType = originalReadType.toNonNull();
DartType inferredType = _analyzeIfNullTypes(
nonNullableReadType: nonNullableReadType,
rhsType: writeResult.inferredType,
typeContext: typeContext);
Expression replacement;
if (node.forEffect) {
// Encode `a ??= b` as:
//
// a == null ? a = b : null
//
Expression equalsNull =
createEqualsNull(read, fileOffset: node.fileOffset);
replacement = new ConditionalExpression(
equalsNull,
writeResult.expression,
new NullLiteral()..fileOffset = node.fileOffset,
computeNullable(inferredType))
..fileOffset = node.fileOffset;
} else {
// Encode `a ??= b` as:
//
// let v1 = a in v1 == null ? a = b : v1
//
VariableDeclaration readVariable = createVariable(read, readType);
Expression equalsNull = createEqualsNull(createVariableGet(readVariable),
fileOffset: node.fileOffset);
VariableGet variableGet = createVariableGet(readVariable);
if (isNonNullableByDefault &&
!identical(nonNullableReadType, originalReadType)) {
variableGet.promotedType = nonNullableReadType;
}
ConditionalExpression conditional = new ConditionalExpression(
equalsNull, writeResult.expression, variableGet, inferredType)
..fileOffset = node.fileOffset;
replacement = new Let(readVariable, conditional)
..fileOffset = node.fileOffset;
}
return createNullAwareExpressionInferenceResult(
inferredType, replacement, nullAwareGuards);
}
ExpressionInferenceResult visitIndexGet(IndexGet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferNullAwareExpression(
node.receiver, const UnknownType(),
isVoidAllowed: true);
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
ObjectAccessTarget indexGetTarget = findInterfaceMember(
receiverType, indexGetName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
DartType indexType = indexGetTarget.getIndexKeyType(this);
MethodContravarianceCheckKind readCheckKind =
preCheckInvocationContravariance(receiverType, indexGetTarget,
isThisReceiver: node.receiver is ThisExpression);
ExpressionInferenceResult indexResult =
inferExpression(node.index, indexType, isVoidAllowed: true);
Expression index =
ensureAssignableResult(indexType, indexResult).expression;
ExpressionInferenceResult replacement = _computeIndexGet(
node.fileOffset,
receiver,
receiverType,
indexGetTarget,
index,
indexType,
readCheckKind);
return createNullAwareExpressionInferenceResult(
replacement.inferredType, replacement.expression, nullAwareGuards);
}
ExpressionInferenceResult visitIndexSet(IndexSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferNullAwareExpression(
node.receiver, const UnknownType(),
isVoidAllowed: true);
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
VariableDeclaration? receiverVariable;
if (!node.forEffect && !isPureExpression(receiver)) {
receiverVariable = createVariable(receiver, receiverType);
receiver = createVariableGet(receiverVariable);
}
ObjectAccessTarget indexSetTarget = findInterfaceMember(
receiverType, indexSetName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
DartType indexType = indexSetTarget.getIndexKeyType(this);
DartType valueType = indexSetTarget.getIndexSetValueType(this);
ExpressionInferenceResult indexResult =
inferExpression(node.index, indexType, isVoidAllowed: true);
Expression index =
ensureAssignableResult(indexType, indexResult).expression;
VariableDeclaration? indexVariable;
if (!node.forEffect && !isPureExpression(index)) {
indexVariable = createVariable(index, indexResult.inferredType);
index = createVariableGet(indexVariable);
}
ExpressionInferenceResult valueResult =
inferExpression(node.value, valueType, isVoidAllowed: true);
valueResult = ensureAssignableResult(valueType, valueResult);
Expression value = valueResult.expression;
VariableDeclaration? valueVariable;
Expression? returnedValue;
if (node.forEffect) {
} else if (isPureExpression(value)) {
returnedValue = clonePureExpression(value);
} else {
valueVariable = createVariable(value, valueResult.inferredType);
value = createVariableGet(valueVariable);
returnedValue = createVariableGet(valueVariable);
}
// The inferred type is that inferred type of the value expression and not
// the type of the value parameter.
DartType inferredType = valueResult.inferredType;
Expression assignment = _computeIndexSet(node.fileOffset, receiver,
receiverType, indexSetTarget, index, indexType, value, valueType);
Expression replacement;
if (node.forEffect) {
replacement = assignment;
} else {
VariableDeclaration assignmentVariable =
createVariable(assignment, const VoidType());
replacement = createLet(assignmentVariable, returnedValue!);
if (valueVariable != null) {
replacement = createLet(valueVariable, replacement);
}
if (indexVariable != null) {
replacement = createLet(indexVariable, replacement);
}
if (receiverVariable != null) {
replacement = createLet(receiverVariable, replacement);
}
}
replacement.fileOffset = node.fileOffset;
return createNullAwareExpressionInferenceResult(
inferredType, replacement, nullAwareGuards);
}
ExpressionInferenceResult visitSuperIndexSet(
SuperIndexSet node, DartType typeContext) {
ObjectAccessTarget indexSetTarget = thisType!.classNode.isMixinDeclaration
? new ObjectAccessTarget.interfaceMember(thisType!, node.setter,
hasNonObjectMemberAccess: true)
: new ObjectAccessTarget.superMember(thisType!, node.setter);
DartType indexType = indexSetTarget.getIndexKeyType(this);
DartType valueType = indexSetTarget.getIndexSetValueType(this);
ExpressionInferenceResult indexResult =
inferExpression(node.index, indexType, isVoidAllowed: true);
Expression index =
ensureAssignableResult(indexType, indexResult).expression;
VariableDeclaration? indexVariable;
if (!isPureExpression(index)) {
indexVariable = createVariable(index, indexResult.inferredType);
index = createVariableGet(indexVariable);
}
ExpressionInferenceResult valueResult =
inferExpression(node.value, valueType, isVoidAllowed: true);
valueResult = ensureAssignableResult(valueType, valueResult);
Expression value = valueResult.expression;
VariableDeclaration? valueVariable;
Expression returnedValue;
if (isPureExpression(value)) {
returnedValue = clonePureExpression(value);
} else {
valueVariable = createVariable(value, valueResult.inferredType);
value = createVariableGet(valueVariable);
returnedValue = createVariableGet(valueVariable);
}
// The inferred type is that inferred type of the value expression and not
// the type of the value parameter.
DartType inferredType = valueResult.inferredType;
assert(indexSetTarget.isInstanceMember || indexSetTarget.isSuperMember,
'Unexpected index set target $indexSetTarget.');
instrumentation?.record(uriForInstrumentation, node.fileOffset, 'target',
new InstrumentationValueForMember(node.setter));
Expression assignment = new SuperMethodInvocation(
indexSetName,
new Arguments(<Expression>[index, value])..fileOffset = node.fileOffset,
indexSetTarget.classMember as Procedure)
..fileOffset = node.fileOffset;
VariableDeclaration assignmentVariable =
createVariable(assignment, const VoidType());
Expression replacement = createLet(assignmentVariable, returnedValue);
if (valueVariable != null) {
replacement = createLet(valueVariable, replacement);
}
if (indexVariable != null) {
replacement = createLet(indexVariable, replacement);
}
return new ExpressionInferenceResult(inferredType, replacement);
}
ExpressionInferenceResult visitExtensionIndexSet(
ExtensionIndexSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferExpression(
node.receiver, const UnknownType(),
isVoidAllowed: false);
List<DartType> extensionTypeArguments = computeExtensionTypeArgument(
node.extension, node.explicitTypeArguments, receiverResult.inferredType,
treeNodeForTesting: node);
DartType receiverType =
getExtensionReceiverType(node.extension, extensionTypeArguments);
Expression receiver =
ensureAssignableResult(receiverType, receiverResult).expression;
VariableDeclaration? receiverVariable;
if (!isPureExpression(receiver)) {
receiverVariable = createVariable(receiver, receiverType);
receiver = createVariableGet(receiverVariable);
}
ObjectAccessTarget target = new ExtensionAccessTarget(receiverType,
node.setter, null, ClassMemberKind.Method, extensionTypeArguments);
DartType indexType = target.getIndexKeyType(this);
DartType valueType = target.getIndexSetValueType(this);
ExpressionInferenceResult indexResult =
inferExpression(node.index, indexType, isVoidAllowed: true);
Expression index =
ensureAssignableResult(indexType, indexResult).expression;
ExpressionInferenceResult valueResult =
inferExpression(node.value, valueType, isVoidAllowed: true);
valueResult = ensureAssignableResult(valueType, valueResult);
Expression value = valueResult.expression;
VariableDeclaration? valueVariable;
Expression returnedValue;
if (isPureExpression(value)) {
returnedValue = clonePureExpression(value);
} else {
valueVariable = createVariable(value, valueResult.inferredType);
value = createVariableGet(valueVariable);
returnedValue = createVariableGet(valueVariable);
}
// The inferred type is that inferred type of the value expression and not
// the type of the value parameter.
DartType inferredType = valueResult.inferredType;
Expression assignment = _computeIndexSet(node.fileOffset, receiver,
receiverType, target, index, indexType, value, valueType);
VariableDeclaration assignmentVariable =
createVariable(assignment, const VoidType());
Expression replacement = createLet(assignmentVariable, returnedValue);
if (valueVariable != null) {
replacement = createLet(valueVariable, replacement);
}
if (receiverVariable != null) {
replacement = createLet(receiverVariable, replacement);
}
replacement.fileOffset = node.fileOffset;
return new ExpressionInferenceResult(inferredType, replacement);
}
ExpressionInferenceResult visitIfNullIndexSet(
IfNullIndexSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferNullAwareExpression(
node.receiver, const UnknownType(),
isVoidAllowed: true);
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
VariableDeclaration? receiverVariable;
Expression readReceiver = receiver;
Expression writeReceiver;
if (isPureExpression(readReceiver)) {
writeReceiver = clonePureExpression(readReceiver);
} else {
receiverVariable = createVariable(readReceiver, receiverType);
readReceiver = createVariableGet(receiverVariable);
writeReceiver = createVariableGet(receiverVariable);
}
ObjectAccessTarget readTarget = findInterfaceMember(
receiverType, indexGetName, node.readOffset,
includeExtensionMethods: true, isSetter: false);
MethodContravarianceCheckKind checkKind = preCheckInvocationContravariance(
receiverType, readTarget,
isThisReceiver: node.receiver is ThisExpression);
DartType readIndexType = readTarget.getIndexKeyType(this);
ObjectAccessTarget writeTarget = findInterfaceMember(
receiverType, indexSetName, node.writeOffset,
includeExtensionMethods: true, isSetter: false);
DartType writeIndexType = writeTarget.getIndexKeyType(this);
DartType valueType = writeTarget.getIndexSetValueType(this);
ExpressionInferenceResult indexResult =
inferExpression(node.index, readIndexType, isVoidAllowed: true);
VariableDeclaration? indexVariable;
Expression readIndex = indexResult.expression;
Map<DartType, NonPromotionReason> Function() whyNotPromotedIndex =
flowAnalysis.whyNotPromoted(readIndex);
Expression writeIndex;
if (isPureExpression(readIndex)) {
writeIndex = clonePureExpression(readIndex);
} else {
indexVariable = createVariable(readIndex, indexResult.inferredType);
readIndex = createVariableGet(indexVariable);
writeIndex = createVariableGet(indexVariable);
}
readIndex = ensureAssignable(
readIndexType, indexResult.inferredType, readIndex,
whyNotPromoted: whyNotPromotedIndex);
ExpressionInferenceResult readResult = _computeIndexGet(
node.readOffset,
readReceiver,
receiverType,
readTarget,
readIndex,
readIndexType,
checkKind);
Expression read = readResult.expression;
DartType readType = readResult.inferredType;
flowAnalysis.ifNullExpression_rightBegin(read, readType);
writeIndex = ensureAssignable(
writeIndexType, indexResult.inferredType, writeIndex,
whyNotPromoted: whyNotPromotedIndex);
ExpressionInferenceResult valueResult =
inferExpression(node.value, valueType, isVoidAllowed: true);
valueResult = ensureAssignableResult(valueType, valueResult);
Expression value = valueResult.expression;
flowAnalysis.ifNullExpression_end();
DartType nonNullableReadType = readType.toNonNull();
DartType inferredType = _analyzeIfNullTypes(
nonNullableReadType: nonNullableReadType,
rhsType: valueResult.inferredType,
typeContext: typeContext);
VariableDeclaration? valueVariable;
Expression? returnedValue;
if (node.forEffect) {
// No need for value variable.
} else if (isPureExpression(value)) {
returnedValue = clonePureExpression(value);
} else {
valueVariable = createVariable(value, valueResult.inferredType);
value = createVariableGet(valueVariable);
returnedValue = createVariableGet(valueVariable);
}
Expression write = _computeIndexSet(
node.writeOffset,
writeReceiver,
receiverType,
writeTarget,
writeIndex,
writeIndexType,
value,
valueType);
Expression inner;
if (node.forEffect) {
// Encode `Extension(o)[a] ??= b`, if `node.readOnlyReceiver` is false,
// as:
//
// let receiverVariable = o in
// let indexVariable = a in
// receiverVariable[indexVariable] == null
// ? receiverVariable.[]=(indexVariable, b) : null
//
// and if `node.readOnlyReceiver` is true as:
//
// let indexVariable = a in
// o[indexVariable] == null ? o.[]=(indexVariable, b) : null
//
Expression equalsNull =
createEqualsNull(read, fileOffset: node.testOffset);
ConditionalExpression conditional = new ConditionalExpression(
equalsNull,
write,
new NullLiteral()..fileOffset = node.testOffset,
computeNullable(inferredType))
..fileOffset = node.testOffset;
inner = conditional;
} else {
// Encode `Extension(o)[a] ??= b` as, if `node.readOnlyReceiver` is false,
// as:
//
// let receiverVariable = o in
// let indexVariable = a in
// let readVariable = receiverVariable[indexVariable] in
// readVariable == null
// ? (let valueVariable = b in
// let writeVariable =
// receiverVariable.[]=(indexVariable, valueVariable) in
// valueVariable)
// : readVariable
//
// and if `node.readOnlyReceiver` is true as:
//
// let indexVariable = a in
// let readVariable = o[indexVariable] in
// readVariable == null
// ? (let valueVariable = b in
// let writeVariable = o.[]=(indexVariable, valueVariable) in
// valueVariable)
// : readVariable
//
//
VariableDeclaration readVariable = createVariable(read, readType);
Expression equalsNull = createEqualsNull(createVariableGet(readVariable),
fileOffset: node.testOffset);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
VariableGet variableGet = createVariableGet(readVariable);
if (isNonNullableByDefault && !identical(nonNullableReadType, readType)) {
variableGet.promotedType = nonNullableReadType;
}
Expression result = createLet(writeVariable, returnedValue!);
if (valueVariable != null) {
result = createLet(valueVariable, result);
}
ConditionalExpression conditional = new ConditionalExpression(
equalsNull, result, variableGet, inferredType)
..fileOffset = node.fileOffset;
inner = createLet(readVariable, conditional);
}
if (indexVariable != null) {
inner = createLet(indexVariable, inner);
}
Expression replacement;
if (receiverVariable != null) {
replacement = new Let(receiverVariable, inner)
..fileOffset = node.fileOffset;
} else {
replacement = inner;
}
return createNullAwareExpressionInferenceResult(
inferredType, replacement, nullAwareGuards);
}
ExpressionInferenceResult visitIfNullSuperIndexSet(
IfNullSuperIndexSet node, DartType typeContext) {
ObjectAccessTarget readTarget = node.getter != null
? (thisType!.classNode.isMixinDeclaration
? new ObjectAccessTarget.interfaceMember(thisType!, node.getter!,
hasNonObjectMemberAccess: true)
: new ObjectAccessTarget.superMember(thisType!, node.getter!))
: const ObjectAccessTarget.missing();
DartType readType = readTarget.getReturnType(this);
DartType readIndexType = readTarget.getIndexKeyType(this);
ObjectAccessTarget writeTarget = node.setter != null
? (thisType!.classNode.isMixinDeclaration
? new ObjectAccessTarget.interfaceMember(thisType!, node.setter!,
hasNonObjectMemberAccess: true)
: new ObjectAccessTarget.superMember(thisType!, node.setter!))
: const ObjectAccessTarget.missing();
DartType writeIndexType = writeTarget.getIndexKeyType(this);
DartType valueType = writeTarget.getIndexSetValueType(this);
ExpressionInferenceResult indexResult =
inferExpression(node.index, readIndexType, isVoidAllowed: true);
VariableDeclaration? indexVariable;
Expression readIndex = indexResult.expression;
Expression writeIndex;
if (isPureExpression(readIndex)) {
writeIndex = clonePureExpression(readIndex);
} else {
indexVariable = createVariable(readIndex, indexResult.inferredType);
readIndex = createVariableGet(indexVariable);
writeIndex = createVariableGet(indexVariable);
}
readIndex =
ensureAssignable(readIndexType, indexResult.inferredType, readIndex);
writeIndex =
ensureAssignable(writeIndexType, indexResult.inferredType, writeIndex);
assert(readTarget.isInstanceMember || readTarget.isSuperMember);
instrumentation?.record(uriForInstrumentation, node.readOffset, 'target',
new InstrumentationValueForMember(node.getter!));
Expression read = new SuperMethodInvocation(
indexGetName,
new Arguments(<Expression>[
readIndex,
])
..fileOffset = node.readOffset,
readTarget.classMember as Procedure)
..fileOffset = node.readOffset;
flowAnalysis.ifNullExpression_rightBegin(read, readType);
ExpressionInferenceResult valueResult =
inferExpression(node.value, valueType, isVoidAllowed: true);
valueResult = ensureAssignableResult(valueType, valueResult);
Expression value = valueResult.expression;
flowAnalysis.ifNullExpression_end();
DartType nonNullableReadType = readType.toNonNull();
DartType inferredType = _analyzeIfNullTypes(
nonNullableReadType: nonNullableReadType,
rhsType: valueResult.inferredType,
typeContext: typeContext);
VariableDeclaration? valueVariable;
Expression? returnedValue;
if (node.forEffect) {
// No need for a value variable.
} else if (isPureExpression(value)) {
returnedValue = clonePureExpression(value);
} else {
valueVariable = createVariable(value, valueResult.inferredType);
value = createVariableGet(valueVariable);
returnedValue = createVariableGet(valueVariable);
}
assert(writeTarget.isInstanceMember || writeTarget.isSuperMember);
instrumentation?.record(uriForInstrumentation, node.writeOffset, 'target',
new InstrumentationValueForMember(node.setter!));
Expression write = new SuperMethodInvocation(
indexSetName,
new Arguments(<Expression>[writeIndex, value])
..fileOffset = node.writeOffset,
writeTarget.classMember as Procedure)
..fileOffset = node.writeOffset;
Expression replacement;
if (node.forEffect) {
// Encode `o[a] ??= b` as:
//
// let v1 = a in
// super[v1] == null ? super.[]=(v1, b) : null
//
assert(valueVariable == null);
Expression equalsNull =
createEqualsNull(read, fileOffset: node.testOffset);
replacement = new ConditionalExpression(
equalsNull,
write,
new NullLiteral()..fileOffset = node.testOffset,
computeNullable(inferredType))
..fileOffset = node.testOffset;
} else {
// Encode `o[a] ??= b` as:
//
// let v1 = a in
// let v2 = super[v1] in
// v2 == null
// ? (let v3 = b in
// let _ = super.[]=(v1, v3) in
// v3)
// : v2
//
VariableDeclaration readVariable = createVariable(read, readType);
Expression equalsNull = createEqualsNull(createVariableGet(readVariable),
fileOffset: node.testOffset);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
VariableGet readVariableGet = createVariableGet(readVariable);
if (isNonNullableByDefault && !identical(nonNullableReadType, readType)) {
readVariableGet.promotedType = nonNullableReadType;
}
Expression result = createLet(writeVariable, returnedValue!);
if (valueVariable != null) {
result = createLet(valueVariable, result);
}
ConditionalExpression conditional = new ConditionalExpression(
equalsNull, result, readVariableGet, inferredType)
..fileOffset = node.fileOffset;
replacement = createLet(readVariable, conditional);
}
if (indexVariable != null) {
replacement = createLet(indexVariable, replacement);
}
return new ExpressionInferenceResult(inferredType, replacement);
}
ExpressionInferenceResult visitIfNullExtensionIndexSet(
IfNullExtensionIndexSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferExpression(
node.receiver, const UnknownType(),
isVoidAllowed: false);
List<DartType> extensionTypeArguments = computeExtensionTypeArgument(
node.extension, node.explicitTypeArguments, receiverResult.inferredType,
treeNodeForTesting: node);
DartType receiverType =
getExtensionReceiverType(node.extension, extensionTypeArguments);
Expression receiver =
ensureAssignableResult(receiverType, receiverResult).expression;
VariableDeclaration? receiverVariable;
Expression readReceiver;
Expression writeReceiver;
if (isPureExpression(receiver)) {
readReceiver = receiver;
writeReceiver = clonePureExpression(receiver);
} else {
receiverVariable = createVariable(receiver, receiverType);
readReceiver = createVariableGet(receiverVariable);
writeReceiver = createVariableGet(receiverVariable);
}
ObjectAccessTarget readTarget = node.getter != null
? new ExtensionAccessTarget(receiverType, node.getter!, null,
ClassMemberKind.Method, extensionTypeArguments)
: const ObjectAccessTarget.missing();
DartType readIndexType = readTarget.getIndexKeyType(this);
ObjectAccessTarget writeTarget = node.setter != null
? new ExtensionAccessTarget(receiverType, node.setter!, null,
ClassMemberKind.Method, extensionTypeArguments)
: const ObjectAccessTarget.missing();
DartType writeIndexType = writeTarget.getIndexKeyType(this);
DartType valueType = writeTarget.getIndexSetValueType(this);
ExpressionInferenceResult indexResult =
inferExpression(node.index, readIndexType, isVoidAllowed: true);
VariableDeclaration? indexVariable;
Expression readIndex = indexResult.expression;
Expression writeIndex;
if (isPureExpression(readIndex)) {
writeIndex = clonePureExpression(readIndex);
} else {
indexVariable = createVariable(readIndex, indexResult.inferredType);
readIndex = createVariableGet(indexVariable);
writeIndex = createVariableGet(indexVariable);
}
readIndex =
ensureAssignable(readIndexType, indexResult.inferredType, readIndex);
ExpressionInferenceResult readResult = _computeIndexGet(
node.readOffset,
readReceiver,
receiverType,
readTarget,
readIndex,
readIndexType,
MethodContravarianceCheckKind.none);
Expression read = readResult.expression;
DartType readType = readResult.inferredType;
flowAnalysis.ifNullExpression_rightBegin(read, readType);
writeIndex =
ensureAssignable(writeIndexType, indexResult.inferredType, writeIndex);
ExpressionInferenceResult valueResult =
inferExpression(node.value, valueType, isVoidAllowed: true);
valueResult = ensureAssignableResult(valueType, valueResult);
Expression value = valueResult.expression;
flowAnalysis.ifNullExpression_end();
DartType nonNullableReadType = readType.toNonNull();
DartType inferredType = _analyzeIfNullTypes(
nonNullableReadType: nonNullableReadType,
rhsType: valueResult.inferredType,
typeContext: typeContext);
VariableDeclaration? valueVariable;
Expression? returnedValue;
if (node.forEffect) {
// No need for a value variable.
} else if (isPureExpression(value)) {
returnedValue = clonePureExpression(value);
} else {
valueVariable = createVariable(value, valueResult.inferredType);
value = createVariableGet(valueVariable);
returnedValue = createVariableGet(valueVariable);
}
Expression write = _computeIndexSet(
node.writeOffset,
writeReceiver,
receiverType,
writeTarget,
writeIndex,
writeIndexType,
value,
valueType);
Expression replacement;
if (node.forEffect) {
// Encode `Extension(o)[a] ??= b` as:
//
// let receiverVariable = o;
// let indexVariable = a in
// receiverVariable[indexVariable] == null
// ? receiverVariable.[]=(indexVariable, b) : null
//
assert(valueVariable == null);
Expression equalsNull =
createEqualsNull(read, fileOffset: node.testOffset);
replacement = new ConditionalExpression(
equalsNull,
write,
new NullLiteral()..fileOffset = node.testOffset,
computeNullable(inferredType))
..fileOffset = node.testOffset;
} else {
// Encode `Extension(o)[a] ??= b` as:
//
// let receiverVariable = o;
// let indexVariable = a in
// let readVariable = receiverVariable[indexVariable] in
// readVariable == null
// ? (let valueVariable = b in
// let writeVariable =
// receiverVariable.[]=(indexVariable, valueVariable) in
// valueVariable)
// : readVariable
//
VariableDeclaration readVariable = createVariable(read, readType);
Expression equalsNull = createEqualsNull(createVariableGet(readVariable),
fileOffset: node.testOffset);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
VariableGet readVariableGet = createVariableGet(readVariable);
if (isNonNullableByDefault && !identical(nonNullableReadType, readType)) {
readVariableGet.promotedType = nonNullableReadType;
}
Expression result = createLet(writeVariable, returnedValue!);
if (valueVariable != null) {
result = createLet(valueVariable, result);
}
ConditionalExpression conditional = new ConditionalExpression(
equalsNull, result, readVariableGet, inferredType)
..fileOffset = node.fileOffset;
replacement = createLet(readVariable, conditional);
}
if (indexVariable != null) {
replacement = createLet(indexVariable, replacement);
}
if (receiverVariable != null) {
replacement = new Let(receiverVariable, replacement);
}
replacement.fileOffset = node.fileOffset;
return new ExpressionInferenceResult(inferredType, replacement);
}
bool _isNull(Expression node) {
return node is NullLiteral ||
node is ConstantExpression && node.constant is NullConstant;
}
/// Creates an equals expression of using [left] and [right] as operands.
///
/// [fileOffset] is used as the file offset for created nodes. [leftType] is
/// the already inferred type of the [left] expression. The inferred type of
/// [right] is computed by this method. If [isNot] is `true` the result is
/// negated to perform a != operation.
ExpressionInferenceResult _computeEqualsExpression(
int fileOffset, Expression left, DartType leftType, Expression right,
{required bool isNot}) {
ExpressionInfo<DartType>? equalityInfo =
flowAnalysis.equalityOperand_end(left, leftType);
Expression? equals;
ExpressionInferenceResult rightResult =
inferExpression(right, const UnknownType(), isVoidAllowed: false);
if (_isNull(right)) {
equals = new EqualsNull(left)..fileOffset = fileOffset;
} else if (_isNull(left)) {
equals = new EqualsNull(rightResult.expression)..fileOffset = fileOffset;
}
if (equals != null) {
if (isNot) {
equals = new Not(equals)..fileOffset = fileOffset;
}
flowAnalysis.equalityOperation_end(
equals,
equalityInfo,
flowAnalysis.equalityOperand_end(
rightResult.expression, rightResult.inferredType),
notEqual: isNot);
return new ExpressionInferenceResult(
coreTypes.boolRawType(libraryBuilder.nonNullable), equals);
}
ObjectAccessTarget equalsTarget = findInterfaceMember(
leftType, equalsName, fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(
equalsTarget.isInstanceMember ||
equalsTarget.isObjectMember ||
equalsTarget.isNever,
"Unexpected equals target $equalsTarget for "
"$left ($leftType) == $right.");
if (instrumentation != null && leftType == const DynamicType()) {
instrumentation!.record(uriForInstrumentation, fileOffset, 'target',
new InstrumentationValueForMember(equalsTarget.member!));
}
DartType rightType = equalsTarget.getBinaryOperandType(this);
if (libraryBuilder.isNonNullableByDefault) {
rightType = operations.getNullableType(rightType);
} else {
rightType = operations.getLegacyType(rightType);
}
DartType contextType =
rightType.withDeclaredNullability(libraryBuilder.nullable);
rightResult = ensureAssignableResult(contextType, rightResult,
errorTemplate: templateArgumentTypeNotAssignable,
nullabilityErrorTemplate: templateArgumentTypeNotAssignableNullability,
nullabilityPartErrorTemplate:
templateArgumentTypeNotAssignablePartNullability,
nullabilityNullErrorTemplate:
templateArgumentTypeNotAssignableNullabilityNull,
nullabilityNullTypeErrorTemplate:
templateArgumentTypeNotAssignableNullabilityNullType);
right = rightResult.expression;
FunctionType functionType = equalsTarget.getFunctionType(this);
equals = new EqualsCall(left, right,
functionType: functionType,
interfaceTarget: equalsTarget.classMember as Procedure)
..fileOffset = fileOffset;
if (isNot) {
equals = new Not(equals)..fileOffset = fileOffset;
}
flowAnalysis.equalityOperation_end(equals, equalityInfo,
flowAnalysis.equalityOperand_end(right, rightResult.inferredType),
notEqual: isNot);
return new ExpressionInferenceResult(
equalsTarget.isNever
? const NeverType.nonNullable()
: coreTypes.boolRawType(libraryBuilder.nonNullable),
equals);
}
/// Creates a binary expression of the binary operator with [binaryName] using
/// [left] and [right] as operands.
///
/// [fileOffset] is used as the file offset for created nodes. [leftType] is
/// the already inferred type of the [left] expression. The inferred type of
/// [right] is computed by this method.
ExpressionInferenceResult _computeBinaryExpression(
int fileOffset,
DartType contextType,
Expression left,
DartType leftType,
Name binaryName,
Expression right,
Map<DartType, NonPromotionReason> Function()? whyNotPromoted) {
assert(binaryName != equalsName);
ObjectAccessTarget binaryTarget = findInterfaceMember(
leftType, binaryName, fileOffset,
includeExtensionMethods: true, isSetter: false);
MethodContravarianceCheckKind binaryCheckKind =
preCheckInvocationContravariance(leftType, binaryTarget,
isThisReceiver: false);
DartType binaryType = binaryTarget.getReturnType(this);
DartType rightType = binaryTarget.getBinaryOperandType(this);
bool isSpecialCasedBinaryOperator =
binaryTarget.isSpecialCasedBinaryOperator(this);
DartType rightContextType = rightType;
if (isSpecialCasedBinaryOperator) {
rightContextType =
typeSchemaEnvironment.getContextTypeOfSpecialCasedBinaryOperator(
contextType, leftType, rightType,
isNonNullableByDefault: isNonNullableByDefault);
}
ExpressionInferenceResult rightResult =
inferExpression(right, rightContextType, isVoidAllowed: true);
rightResult = ensureAssignableResult(rightType, rightResult);
right = rightResult.expression;
if (isSpecialCasedBinaryOperator) {
binaryType = typeSchemaEnvironment.getTypeOfSpecialCasedBinaryOperator(
leftType, rightResult.inferredType,
isNonNullableByDefault: isNonNullableByDefault);
}
if (!isNonNullableByDefault) {
binaryType = legacyErasure(binaryType);
}
Expression binary;
switch (binaryTarget.kind) {
case ObjectAccessTargetKind.missing:
binary =
createMissingBinary(fileOffset, left, leftType, binaryName, right);
break;
case ObjectAccessTargetKind.ambiguous:
binary = createMissingBinary(
fileOffset, left, leftType, binaryName, right,
extensionAccessCandidates: binaryTarget.candidates);
break;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
case ObjectAccessTargetKind.extensionTypeMember:
case ObjectAccessTargetKind.nullableExtensionTypeMember:
assert(binaryTarget.declarationMethodKind != ClassMemberKind.Setter);
binary = new StaticInvocation(
binaryTarget.member as Procedure,
new Arguments(<Expression>[
left,
right,
], types: binaryTarget.receiverTypeArguments)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.invalid:
binary = new DynamicInvocation(
DynamicAccessKind.Invalid,
left,
binaryName,
new Arguments(<Expression>[
right,
])
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.dynamic:
binary = new DynamicInvocation(
DynamicAccessKind.Dynamic,
left,
binaryName,
new Arguments(<Expression>[
right,
])
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.never:
binary = new DynamicInvocation(
DynamicAccessKind.Never,
left,
binaryName,
new Arguments(<Expression>[
right,
])
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
case ObjectAccessTargetKind.superMember:
if ((binaryTarget.isInstanceMember || binaryTarget.isObjectMember) &&
instrumentation != null &&
leftType == const DynamicType()) {
instrumentation!.record(uriForInstrumentation, fileOffset, 'target',
new InstrumentationValueForMember(binaryTarget.member!));
}
binary = new InstanceInvocation(
InstanceAccessKind.Instance,
left,
binaryName,
new Arguments(<Expression>[
right,
])
..fileOffset = fileOffset,
functionType: new FunctionType(
[rightType], binaryType, libraryBuilder.nonNullable),
interfaceTarget: binaryTarget.classMember as Procedure)
..fileOffset = fileOffset;
if (binaryCheckKind ==
MethodContravarianceCheckKind.checkMethodReturn) {
if (instrumentation != null) {
instrumentation!.record(uriForInstrumentation, fileOffset,
'checkReturn', new InstrumentationValueForType(binaryType));
}
binary = new AsExpression(binary, binaryType)
..isTypeError = true
..isCovarianceCheck = true
..isForNonNullableByDefault = isNonNullableByDefault
..fileOffset = fileOffset;
}
break;
case ObjectAccessTargetKind.recordIndexed:
case ObjectAccessTargetKind.recordNamed:
case ObjectAccessTargetKind.nullableRecordIndexed:
case ObjectAccessTargetKind.nullableRecordNamed:
case ObjectAccessTargetKind.extensionTypeRepresentation:
case ObjectAccessTargetKind.nullableExtensionTypeRepresentation:
throw new UnsupportedError('Unexpected binary target ${binaryTarget}');
}
if (binaryTarget.isNullable) {
List<LocatedMessage>? context = getWhyNotPromotedContext(
whyNotPromoted?.call(),
binary,
(type) => !type.isPotentiallyNullable);
return new ExpressionInferenceResult(
binaryType,
helper.wrapInProblem(
binary,
templateNullableOperatorCallError.withArguments(
binaryName.text, leftType, isNonNullableByDefault),
binary.fileOffset,
binaryName.text.length,
context: context));
}
return new ExpressionInferenceResult(binaryType, binary);
}
/// Creates a unary expression of the unary operator with [unaryName] using
/// [expression] as the operand.
///
/// [fileOffset] is used as the file offset for created nodes.
/// [expressionType] is the already inferred type of the [expression].
ExpressionInferenceResult _computeUnaryExpression(
int fileOffset,
Expression expression,
DartType expressionType,
Name unaryName,
Map<DartType, NonPromotionReason> Function() whyNotPromoted) {
ObjectAccessTarget unaryTarget = findInterfaceMember(
expressionType, unaryName, fileOffset,
includeExtensionMethods: true, isSetter: false);
MethodContravarianceCheckKind unaryCheckKind =
preCheckInvocationContravariance(expressionType, unaryTarget,
isThisReceiver: false);
DartType unaryType = unaryTarget.getReturnType(this);
Expression unary;
switch (unaryTarget.kind) {
case ObjectAccessTargetKind.missing:
unary = createMissingUnary(
fileOffset, expression, expressionType, unaryName);
break;
case ObjectAccessTargetKind.ambiguous:
unary = createMissingUnary(
fileOffset, expression, expressionType, unaryName,
extensionAccessCandidates: unaryTarget.candidates);
break;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
case ObjectAccessTargetKind.extensionTypeMember:
case ObjectAccessTargetKind.nullableExtensionTypeMember:
assert(unaryTarget.declarationMethodKind != ClassMemberKind.Setter);
unary = new StaticInvocation(
unaryTarget.member as Procedure,
new Arguments(<Expression>[
expression,
], types: unaryTarget.receiverTypeArguments)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.invalid:
unary = new DynamicInvocation(DynamicAccessKind.Invalid, expression,
unaryName, new Arguments(<Expression>[])..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.never:
unary = new DynamicInvocation(DynamicAccessKind.Never, expression,
unaryName, new Arguments(<Expression>[])..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.dynamic:
unary = new DynamicInvocation(DynamicAccessKind.Dynamic, expression,
unaryName, new Arguments(<Expression>[])..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
case ObjectAccessTargetKind.superMember:
if ((unaryTarget.isInstanceMember || unaryTarget.isObjectMember) &&
instrumentation != null &&
expressionType == const DynamicType()) {
instrumentation!.record(uriForInstrumentation, fileOffset, 'target',
new InstrumentationValueForMember(unaryTarget.member!));
}
unary = new InstanceInvocation(InstanceAccessKind.Instance, expression,
unaryName, new Arguments(<Expression>[])..fileOffset = fileOffset,
functionType: new FunctionType(
<DartType>[], unaryType, libraryBuilder.nonNullable),
interfaceTarget: unaryTarget.classMember as Procedure)
..fileOffset = fileOffset;
if (unaryCheckKind == MethodContravarianceCheckKind.checkMethodReturn) {
if (instrumentation != null) {
instrumentation!.record(uriForInstrumentation, fileOffset,
'checkReturn', new InstrumentationValueForType(expressionType));
}
unary = new AsExpression(unary, unaryType)
..isTypeError = true
..isCovarianceCheck = true
..isForNonNullableByDefault = isNonNullableByDefault
..fileOffset = fileOffset;
}
break;
case ObjectAccessTargetKind.recordIndexed:
case ObjectAccessTargetKind.recordNamed:
case ObjectAccessTargetKind.nullableRecordIndexed:
case ObjectAccessTargetKind.nullableRecordNamed:
case ObjectAccessTargetKind.extensionTypeRepresentation:
case ObjectAccessTargetKind.nullableExtensionTypeRepresentation:
throw new UnsupportedError('Unexpected unary target ${unaryTarget}');
}
if (!isNonNullableByDefault) {
unaryType = legacyErasure(unaryType);
}
if (unaryTarget.isNullable) {
List<LocatedMessage>? context = getWhyNotPromotedContext(
whyNotPromoted(), unary, (type) => !type.isPotentiallyNullable);
// TODO(johnniwinther): Special case 'unary-' in messages. It should
// probably be referred to as "Unary operator '-' ...".
return new ExpressionInferenceResult(
unaryType,
helper.wrapInProblem(
unary,
templateNullableOperatorCallError.withArguments(
unaryName.text, expressionType, isNonNullableByDefault),
unary.fileOffset,
unaryName == unaryMinusName ? 1 : unaryName.text.length,
context: context));
}
return new ExpressionInferenceResult(unaryType, unary);
}
/// Creates an index operation of [readTarget] on [receiver] using [index] as
/// the argument.
///
/// [fileOffset] is used as the file offset for created nodes. [receiverType]
/// is the already inferred type of the [receiver] expression. The inferred
/// type of [index] must already have been computed.
ExpressionInferenceResult _computeIndexGet(
int fileOffset,
Expression readReceiver,
DartType receiverType,
ObjectAccessTarget readTarget,
Expression readIndex,
DartType indexType,
MethodContravarianceCheckKind readCheckKind) {
Expression read;
DartType readType = readTarget.getReturnType(this);
switch (readTarget.kind) {
case ObjectAccessTargetKind.missing:
read = createMissingIndexGet(
fileOffset, readReceiver, receiverType, readIndex);
break;
case ObjectAccessTargetKind.ambiguous:
read = createMissingIndexGet(
fileOffset, readReceiver, receiverType, readIndex,
extensionAccessCandidates: readTarget.candidates);
break;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
case ObjectAccessTargetKind.extensionTypeMember:
case ObjectAccessTargetKind.nullableExtensionTypeMember:
read = new StaticInvocation(
readTarget.member as Procedure,
new Arguments(<Expression>[
readReceiver,
readIndex,
], types: readTarget.receiverTypeArguments)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.invalid:
read = new DynamicInvocation(
DynamicAccessKind.Invalid,
readReceiver,
indexGetName,
new Arguments(<Expression>[
readIndex,
])
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.never:
read = new DynamicInvocation(
DynamicAccessKind.Never,
readReceiver,
indexGetName,
new Arguments(<Expression>[
readIndex,
])
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.dynamic:
read = new DynamicInvocation(
DynamicAccessKind.Dynamic,
readReceiver,
indexGetName,
new Arguments(<Expression>[
readIndex,
])
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
case ObjectAccessTargetKind.superMember:
InstanceAccessKind kind;
switch (readTarget.kind) {
case ObjectAccessTargetKind.instanceMember:
kind = InstanceAccessKind.Instance;
break;
case ObjectAccessTargetKind.nullableInstanceMember:
kind = InstanceAccessKind.Nullable;
break;
case ObjectAccessTargetKind.objectMember:
kind = InstanceAccessKind.Object;
break;
default:
throw new UnsupportedError('Unexpected target kind $readTarget');
}
read = new InstanceInvocation(
kind,
readReceiver,
indexGetName,
new Arguments(<Expression>[
readIndex,
])
..fileOffset = fileOffset,
functionType: new FunctionType(
[indexType], readType, libraryBuilder.nonNullable),
interfaceTarget: readTarget.classMember as Procedure)
..fileOffset = fileOffset;
if (readCheckKind == MethodContravarianceCheckKind.checkMethodReturn) {
if (instrumentation != null) {
instrumentation!.record(uriForInstrumentation, fileOffset,
'checkReturn', new InstrumentationValueForType(readType));
}
read = new AsExpression(read, readType)
..isTypeError = true
..isCovarianceCheck = true
..isForNonNullableByDefault = isNonNullableByDefault
..fileOffset = fileOffset;
}
break;
case ObjectAccessTargetKind.recordIndexed:
case ObjectAccessTargetKind.recordNamed:
case ObjectAccessTargetKind.nullableRecordIndexed:
case ObjectAccessTargetKind.nullableRecordNamed:
case ObjectAccessTargetKind.extensionTypeRepresentation:
case ObjectAccessTargetKind.nullableExtensionTypeRepresentation:
throw new UnsupportedError('Unexpected index get target ${readTarget}');
}
if (!isNonNullableByDefault) {
readType = legacyErasure(readType);
}
if (readTarget.isNullable) {
return new ExpressionInferenceResult(
readType,
helper.wrapInProblem(
read,
templateNullableOperatorCallError.withArguments(
indexGetName.text, receiverType, isNonNullableByDefault),
read.fileOffset,
noLength));
}
return new ExpressionInferenceResult(readType, read);
}
/// Creates an index set operation of [writeTarget] on [receiver] using
/// [index] and [value] as the arguments.
///
/// [fileOffset] is used as the file offset for created nodes. [receiverType]
/// is the already inferred type of the [receiver] expression. The inferred
/// type of [index] and [value] must already have been computed.
Expression _computeIndexSet(
int fileOffset,
Expression receiver,
DartType receiverType,
ObjectAccessTarget writeTarget,
Expression index,
DartType indexType,
Expression value,
DartType valueType) {
Expression write;
switch (writeTarget.kind) {
case ObjectAccessTargetKind.missing:
write = createMissingIndexSet(
fileOffset, receiver, receiverType, index, value,
forEffect: true);
break;
case ObjectAccessTargetKind.ambiguous:
write = createMissingIndexSet(
fileOffset, receiver, receiverType, index, value,
forEffect: true, extensionAccessCandidates: writeTarget.candidates);
break;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
case ObjectAccessTargetKind.extensionTypeMember:
case ObjectAccessTargetKind.nullableExtensionTypeMember:
assert(writeTarget.declarationMethodKind != ClassMemberKind.Setter);
write = new StaticInvocation(
writeTarget.member as Procedure,
new Arguments(<Expression>[receiver, index, value],
types: writeTarget.receiverTypeArguments)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.invalid:
write = new DynamicInvocation(
DynamicAccessKind.Invalid,
receiver,
indexSetName,
new Arguments(<Expression>[index, value])..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.never:
write = new DynamicInvocation(
DynamicAccessKind.Never,
receiver,
indexSetName,
new Arguments(<Expression>[index, value])..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.dynamic:
write = new DynamicInvocation(
DynamicAccessKind.Dynamic,
receiver,
indexSetName,
new Arguments(<Expression>[index, value])..fileOffset = fileOffset)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
case ObjectAccessTargetKind.superMember:
InstanceAccessKind kind;
switch (writeTarget.kind) {
case ObjectAccessTargetKind.instanceMember:
kind = InstanceAccessKind.Instance;
break;
case ObjectAccessTargetKind.nullableInstanceMember:
kind = InstanceAccessKind.Nullable;
break;
case ObjectAccessTargetKind.objectMember:
kind = InstanceAccessKind.Object;
break;
default:
throw new UnsupportedError('Unexpected target kind $writeTarget');
}
write = new InstanceInvocation(kind, receiver, indexSetName,
new Arguments(<Expression>[index, value])..fileOffset = fileOffset,
functionType: new FunctionType([indexType, valueType],
const VoidType(), libraryBuilder.nonNullable),
interfaceTarget: writeTarget.classMember as Procedure)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.recordIndexed:
case ObjectAccessTargetKind.recordNamed:
case ObjectAccessTargetKind.nullableRecordIndexed:
case ObjectAccessTargetKind.nullableRecordNamed:
case ObjectAccessTargetKind.extensionTypeRepresentation:
case ObjectAccessTargetKind.nullableExtensionTypeRepresentation:
throw new UnsupportedError(
'Unexpected index set target ${writeTarget}');
}
if (writeTarget.isNullable) {
return helper.wrapInProblem(
write,
templateNullableOperatorCallError.withArguments(
indexSetName.text, receiverType, isNonNullableByDefault),
write.fileOffset,
noLength);
}
return write;
}
/// Creates a property get of [propertyName] on [receiver] of type
/// [receiverType].
///
/// [fileOffset] is used as the file offset for created nodes. [receiverType]
/// is the already inferred type of the [receiver] expression. The
/// [typeContext] is used to create implicit generic tearoff instantiation
/// if necessary. [isThisReceiver] must be set to `true` if the receiver is a
/// `this` expression.
PropertyGetInferenceResult _computePropertyGet(
int fileOffset,
Expression receiver,
DartType receiverType,
Name propertyName,
DartType typeContext,
{required bool isThisReceiver,
ObjectAccessTarget? readTarget,
Expression? propertyGetNode}) {
Map<DartType, NonPromotionReason> Function() whyNotPromoted =
flowAnalysis.whyNotPromoted(receiver);
readTarget ??= findInterfaceMember(receiverType, propertyName, fileOffset,
includeExtensionMethods: true, isSetter: false);
DartType readType = readTarget.getGetterType(this);
DartType? promotedReadType = flowAnalysis.propertyGet(
propertyGetNode,
computePropertyTarget(receiver),
propertyName.text,
readTarget is ExtensionTypeRepresentationAccessTarget
? readTarget.representationField
: readTarget.member,
readType);
return createPropertyGet(
fileOffset: fileOffset,
receiver: receiver,
receiverType: receiverType,
propertyName: propertyName,
typeContext: typeContext,
readTarget: readTarget,
readType: readType,
promotedReadType: promotedReadType,
isThisReceiver: isThisReceiver,
whyNotPromoted: whyNotPromoted);
}
/// Creates a property set operation of [writeTarget] on [receiver] using
/// [value] as the right-hand side.
///
/// [fileOffset] is used as the file offset for created nodes. [propertyName]
/// is used for error reporting. [receiverType] is the already inferred type
/// of the [receiver] expression. The inferred type of [value] must already
/// have been computed.
///
/// If [forEffect] the resulting expression is ensured to return the [value]
/// of static type [valueType]. This is needed for extension setters which are
/// encoded as static method calls that do not implicitly return the value.
///
/// The returned [ExpressionInferenceResult] holds the generated expression
/// and the type of this expression. Normally this is the [valueType] but
/// for setter extension for effect, the generated expression has type
/// `void`.
ExpressionInferenceResult _computePropertySet(
int fileOffset,
Expression receiver,
DartType receiverType,
Name propertyName,
ObjectAccessTarget writeTarget,
Expression value,
{required DartType valueType,
required bool forEffect}) {
Expression write;
DartType writeType = valueType;
switch (writeTarget.kind) {
case ObjectAccessTargetKind.missing:
write = createMissingPropertySet(
fileOffset, receiver, receiverType, propertyName, value,
forEffect: forEffect);
break;
case ObjectAccessTargetKind.ambiguous:
write = createMissingPropertySet(
fileOffset, receiver, receiverType, propertyName, value,
forEffect: forEffect,
extensionAccessCandidates: writeTarget.candidates);
break;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
case ObjectAccessTargetKind.extensionTypeMember:
case ObjectAccessTargetKind.nullableExtensionTypeMember:
if (forEffect) {
write = new StaticInvocation(
writeTarget.member as Procedure,
new Arguments(<Expression>[receiver, value],
types: writeTarget.receiverTypeArguments)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
// The generate invocation has a void return type.
writeType = const VoidType();
} else {
VariableDeclaration valueVariable = createVariable(value, valueType);
VariableDeclaration assignmentVariable = createVariable(
new StaticInvocation(
writeTarget.member as Procedure,
new Arguments(
<Expression>[receiver, createVariableGet(valueVariable)],
types: writeTarget.receiverTypeArguments)
..fileOffset = fileOffset)
..fileOffset = fileOffset,
const VoidType());
write = createLet(valueVariable,
createLet(assignmentVariable, createVariableGet(valueVariable)))
..fileOffset = fileOffset;
}
break;
case ObjectAccessTargetKind.invalid:
write = new DynamicSet(
DynamicAccessKind.Invalid, receiver, propertyName, value)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.never:
write = new DynamicSet(
DynamicAccessKind.Never, receiver, propertyName, value)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.dynamic:
write = new DynamicSet(
DynamicAccessKind.Dynamic, receiver, propertyName, value)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
case ObjectAccessTargetKind.superMember:
InstanceAccessKind kind;
switch (writeTarget.kind) {
case ObjectAccessTargetKind.instanceMember:
kind = InstanceAccessKind.Instance;
break;
case ObjectAccessTargetKind.nullableInstanceMember:
kind = InstanceAccessKind.Nullable;
break;
case ObjectAccessTargetKind.objectMember:
kind = InstanceAccessKind.Object;
break;
default:
throw new UnsupportedError('Unexpected target kind $writeTarget');
}
write = new InstanceSet(kind, receiver, propertyName, value,
interfaceTarget: writeTarget.classMember!)
..fileOffset = fileOffset;
break;
case ObjectAccessTargetKind.recordIndexed:
case ObjectAccessTargetKind.recordNamed:
case ObjectAccessTargetKind.extensionTypeRepresentation:
case ObjectAccessTargetKind.nullableRecordIndexed:
case ObjectAccessTargetKind.nullableRecordNamed:
case ObjectAccessTargetKind.nullableExtensionTypeRepresentation:
throw new UnsupportedError('Unexpected write target ${writeTarget}');
}
Expression result;
if (writeTarget.isNullable) {
result = helper.wrapInProblem(
write,
templateNullablePropertyAccessError.withArguments(
propertyName.text, receiverType, isNonNullableByDefault),
write.fileOffset,
propertyName.text.length);
} else {
result = write;
}
return new ExpressionInferenceResult(writeType, result);
}
ExpressionInferenceResult visitCompoundIndexSet(
CompoundIndexSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferNullAwareExpression(
node.receiver, const UnknownType(),
isVoidAllowed: true);
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
VariableDeclaration? receiverVariable;
Expression readReceiver = receiver;
Expression writeReceiver;
if (isPureExpression(readReceiver)) {
writeReceiver = clonePureExpression(readReceiver);
} else {
receiverVariable = createVariable(readReceiver, receiverType);
readReceiver = createVariableGet(receiverVariable);
writeReceiver = createVariableGet(receiverVariable);
}
ObjectAccessTarget readTarget = findInterfaceMember(
receiverType, indexGetName, node.readOffset,
includeExtensionMethods: true, isSetter: false);
MethodContravarianceCheckKind readCheckKind =
preCheckInvocationContravariance(receiverType, readTarget,
isThisReceiver: node.receiver is ThisExpression);
DartType readIndexType = readTarget.getIndexKeyType(this);
ExpressionInferenceResult indexResult =
inferExpression(node.index, readIndexType, isVoidAllowed: true);
VariableDeclaration? indexVariable;
Expression readIndex = indexResult.expression;
Map<DartType, NonPromotionReason> Function() whyNotPromotedIndex =
flowAnalysis.whyNotPromoted(readIndex);
Expression writeIndex;
if (isPureExpression(readIndex)) {
writeIndex = clonePureExpression(readIndex);
} else {
indexVariable = createVariable(readIndex, indexResult.inferredType);
readIndex = createVariableGet(indexVariable);
writeIndex = createVariableGet(indexVariable);
}
readIndex = ensureAssignable(
readIndexType, indexResult.inferredType, readIndex,
whyNotPromoted: whyNotPromotedIndex);
ExpressionInferenceResult readResult = _computeIndexGet(
node.readOffset,
readReceiver,
receiverType,
readTarget,
readIndex,
readIndexType,
readCheckKind);
Expression read = readResult.expression;
DartType readType = readResult.inferredType;
VariableDeclaration? leftVariable;
Expression left;
if (node.forEffect) {
left = read;
} else if (node.forPostIncDec) {
leftVariable = createVariable(read, readType);
left = createVariableGet(leftVariable);
} else {
left = read;
}
ObjectAccessTarget writeTarget = findInterfaceMember(
receiverType, indexSetName, node.writeOffset,
includeExtensionMethods: true, isSetter: false);
DartType writeIndexType = writeTarget.getIndexKeyType(this);
DartType valueType = writeTarget.getIndexSetValueType(this);
ExpressionInferenceResult binaryResult = _computeBinaryExpression(
node.binaryOffset,
valueType,
left,
readType,
node.binaryName,
node.rhs,
null);
writeIndex = ensureAssignable(
writeIndexType, indexResult.inferredType, writeIndex,
whyNotPromoted: whyNotPromotedIndex);
binaryResult = ensureAssignableResult(valueType, binaryResult,
fileOffset: node.fileOffset);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
VariableDeclaration? valueVariable;
Expression valueExpression;
if (node.forEffect || node.forPostIncDec) {
valueExpression = binary;
} else {
valueVariable = createVariable(binary, binaryType);
valueExpression = createVariableGet(valueVariable);
}
Expression write = _computeIndexSet(
node.writeOffset,
writeReceiver,
receiverType,
writeTarget,
writeIndex,
writeIndexType,
valueExpression,
valueType);
Expression inner;
if (node.forEffect) {
assert(leftVariable == null);
assert(valueVariable == null);
// Encode `o[a] += b` as:
//
// let v1 = o in let v2 = a in v1.[]=(v2, v1.[](v2) + b)
//
inner = write;
} else if (node.forPostIncDec) {
// Encode `o[a]++` as:
//
// let v1 = o in
// let v2 = a in
// let v3 = v1.[](v2)
// let v4 = v1.[]=(v2, c3 + b) in v3
//
assert(leftVariable != null);
assert(valueVariable == null);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
inner = createLet(leftVariable!,
createLet(writeVariable, createVariableGet(leftVariable)));
} else {
// Encode `o[a] += b` as:
//
// let v1 = o in
// let v2 = a in
// let v3 = v1.[](v2) + b
// let v4 = v1.[]=(v2, c3) in v3
//
assert(leftVariable == null);
assert(valueVariable != null);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
inner = createLet(valueVariable!,
createLet(writeVariable, createVariableGet(valueVariable)));
}
if (indexVariable != null) {
inner = createLet(indexVariable, inner);
}
Expression replacement;
if (receiverVariable != null) {
replacement = new Let(receiverVariable, inner)
..fileOffset = node.fileOffset;
} else {
replacement = inner;
}
return createNullAwareExpressionInferenceResult(
node.forPostIncDec ? readType : binaryType,
replacement,
nullAwareGuards);
}
ExpressionInferenceResult visitNullAwareCompoundSet(
NullAwareCompoundSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferNullAwareExpression(
node.receiver, const UnknownType(),
isVoidAllowed: true);
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
VariableDeclaration? receiverVariable =
createVariable(receiver, receiverType);
NullAwareGuard nullAwareGuard = createNullAwareGuard(receiverVariable);
Expression readReceiver = createVariableGet(receiverVariable);
Expression writeReceiver = createVariableGet(receiverVariable);
DartType nonNullReceiverType = receiverType.toNonNull();
ExpressionInferenceResult readResult = _computePropertyGet(
node.readOffset,
readReceiver,
nonNullReceiverType,
node.propertyName,
const UnknownType(),
isThisReceiver: node.receiver is ThisExpression)
.expressionInferenceResult;
Expression read = readResult.expression;
DartType readType = readResult.inferredType;
VariableDeclaration? leftVariable;
Expression left;
if (node.forEffect) {
left = read;
} else if (node.forPostIncDec) {
leftVariable = createVariable(read, readType);
left = createVariableGet(leftVariable);
} else {
left = read;
}
ObjectAccessTarget writeTarget = findInterfaceMember(
nonNullReceiverType, node.propertyName, node.writeOffset,
isSetter: true, includeExtensionMethods: true);
DartType valueType = writeTarget.getSetterType(this);
ExpressionInferenceResult binaryResult = _computeBinaryExpression(
node.binaryOffset,
valueType,
left,
readType,
node.binaryName,
node.rhs,
null);
binaryResult = ensureAssignableResult(valueType, binaryResult,
fileOffset: node.fileOffset);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
VariableDeclaration? valueVariable;
Expression valueExpression;
if (node.forEffect || node.forPostIncDec) {
valueExpression = binary;
} else {
valueVariable = createVariable(binary, binaryType);
valueExpression = createVariableGet(valueVariable);
}
ExpressionInferenceResult writeResult = _computePropertySet(
node.writeOffset,
writeReceiver,
nonNullReceiverType,
node.propertyName,
writeTarget,
valueExpression,
valueType: binaryType,
forEffect: true);
Expression write = writeResult.expression;
DartType writeType = writeResult.inferredType;
DartType resultType = node.forPostIncDec ? readType : binaryType;
Expression action;
if (node.forEffect) {
assert(leftVariable == null);
assert(valueVariable == null);
// Encode `receiver?.propertyName binaryName= rhs` as:
//
// let receiverVariable = receiver in
// receiverVariable == null ? null :
// receiverVariable.propertyName =
// receiverVariable.propertyName + rhs
//
action = write;
} else if (node.forPostIncDec) {
// Encode `receiver?.propertyName binaryName= rhs` from a postfix
// expression like `o?.a++` as:
//
// let receiverVariable = receiver in
// receiverVariable == null ? null :
// let leftVariable = receiverVariable.propertyName in
// let writeVariable =
// receiverVariable.propertyName =
// leftVariable binaryName rhs in
// leftVariable
//
assert(leftVariable != null);
assert(valueVariable == null);
VariableDeclaration writeVariable = createVariable(write, writeType);
action = createLet(leftVariable!,
createLet(writeVariable, createVariableGet(leftVariable)));
} else {
// Encode `receiver?.propertyName binaryName= rhs` as:
//
// let receiverVariable = receiver in
// receiverVariable == null ? null :
// let leftVariable = receiverVariable.propertyName in
// let valueVariable = leftVariable binaryName rhs in
// let writeVariable =
// receiverVariable.propertyName = valueVariable in
// valueVariable
//
// TODO(johnniwinther): Do we need the `leftVariable` in this case?
assert(leftVariable == null);
assert(valueVariable != null);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
action = createLet(valueVariable!,
createLet(writeVariable, createVariableGet(valueVariable)));
}
return createNullAwareExpressionInferenceResult(
resultType, action, nullAwareGuards.prepend(nullAwareGuard));
}
ExpressionInferenceResult visitCompoundSuperIndexSet(
CompoundSuperIndexSet node, DartType typeContext) {
ObjectAccessTarget readTarget = thisType!.classNode.isMixinDeclaration
? new ObjectAccessTarget.interfaceMember(thisType!, node.getter,
hasNonObjectMemberAccess: true)
: new ObjectAccessTarget.superMember(thisType!, node.getter);
DartType readType = readTarget.getReturnType(this);
DartType readIndexType = readTarget.getIndexKeyType(this);
ExpressionInferenceResult indexResult =
inferExpression(node.index, readIndexType, isVoidAllowed: true);
VariableDeclaration? indexVariable;
Expression readIndex = indexResult.expression;
Expression writeIndex;
if (isPureExpression(readIndex)) {
writeIndex = clonePureExpression(readIndex);
} else {
indexVariable = createVariable(readIndex, indexResult.inferredType);
readIndex = createVariableGet(indexVariable);
writeIndex = createVariableGet(indexVariable);
}
readIndex =
ensureAssignable(readIndexType, indexResult.inferredType, readIndex);
assert(readTarget.isInstanceMember || readTarget.isSuperMember);
instrumentation?.record(uriForInstrumentation, node.readOffset, 'target',
new InstrumentationValueForMember(node.getter));
Expression read = new SuperMethodInvocation(
indexGetName,
new Arguments(<Expression>[
readIndex,
])
..fileOffset = node.readOffset,
readTarget.classMember as Procedure)
..fileOffset = node.readOffset;
VariableDeclaration? leftVariable;
Expression left;
if (node.forEffect) {
left = read;
} else if (node.forPostIncDec) {
leftVariable = createVariable(read, readType);
left = createVariableGet(leftVariable);
} else {
left = read;
}
ObjectAccessTarget writeTarget = thisType!.classNode.isMixinDeclaration
? new ObjectAccessTarget.interfaceMember(thisType!, node.setter,
hasNonObjectMemberAccess: true)
: new ObjectAccessTarget.superMember(thisType!, node.setter);
DartType writeIndexType = writeTarget.getIndexKeyType(this);
DartType valueType = writeTarget.getIndexSetValueType(this);
ExpressionInferenceResult binaryResult = _computeBinaryExpression(
node.binaryOffset,
valueType,
left,
readType,
node.binaryName,
node.rhs,
null);
binaryResult = ensureAssignableResult(valueType, binaryResult,
fileOffset: node.fileOffset);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
writeIndex =
ensureAssignable(writeIndexType, indexResult.inferredType, writeIndex);
VariableDeclaration? valueVariable;
Expression valueExpression;
if (node.forEffect || node.forPostIncDec) {
valueExpression = binary;
} else {
valueVariable = createVariable(binary, binaryType);
valueExpression = createVariableGet(valueVariable);
}
assert(writeTarget.isInstanceMember || writeTarget.isSuperMember);
instrumentation?.record(uriForInstrumentation, node.writeOffset, 'target',
new InstrumentationValueForMember(node.setter));
Expression write = new SuperMethodInvocation(
indexSetName,
new Arguments(<Expression>[writeIndex, valueExpression])
..fileOffset = node.writeOffset,
writeTarget.classMember as Procedure)
..fileOffset = node.writeOffset;
Expression replacement;
if (node.forEffect) {
assert(leftVariable == null);
assert(valueVariable == null);
// Encode `super[a] += b` as:
//
// let v1 = a in super.[]=(v1, super.[](v1) + b)
//
replacement = write;
} else if (node.forPostIncDec) {
// Encode `super[a]++` as:
//
// let v2 = a in
// let v3 = v1.[](v2)
// let v4 = v1.[]=(v2, v3 + 1) in v3
//
assert(leftVariable != null);
assert(valueVariable == null);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
replacement = createLet(leftVariable!,
createLet(writeVariable, createVariableGet(leftVariable)));
} else {
// Encode `super[a] += b` as:
//
// let v1 = o in
// let v2 = a in
// let v3 = v1.[](v2) + b
// let v4 = v1.[]=(v2, c3) in v3
//
assert(leftVariable == null);
assert(valueVariable != null);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
replacement = createLet(valueVariable!,
createLet(writeVariable, createVariableGet(valueVariable)));
}
if (indexVariable != null) {
replacement = createLet(indexVariable, replacement);
}
return new ExpressionInferenceResult(
node.forPostIncDec ? readType : binaryType, replacement);
}
ExpressionInferenceResult visitCompoundExtensionIndexSet(
CompoundExtensionIndexSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferExpression(
node.receiver, const UnknownType(),
isVoidAllowed: false);
List<DartType> extensionTypeArguments = computeExtensionTypeArgument(
node.extension, node.explicitTypeArguments, receiverResult.inferredType,
treeNodeForTesting: node);
DartType receiverType =
getExtensionReceiverType(node.extension, extensionTypeArguments);
ObjectAccessTarget readTarget = node.getter != null
? new ExtensionAccessTarget(receiverType, node.getter!, null,
ClassMemberKind.Method, extensionTypeArguments)
: const ObjectAccessTarget.missing();
Expression receiver =
ensureAssignableResult(receiverType, receiverResult).expression;
VariableDeclaration? receiverVariable;
Expression readReceiver;
Expression writeReceiver;
if (isPureExpression(receiver)) {
readReceiver = receiver;
writeReceiver = clonePureExpression(receiver);
} else {
receiverVariable = createVariable(receiver, receiverType);
readReceiver = createVariableGet(receiverVariable);
writeReceiver = createVariableGet(receiverVariable);
}
DartType readIndexType = readTarget.getIndexKeyType(this);
ExpressionInferenceResult indexResult =
inferExpression(node.index, readIndexType, isVoidAllowed: true);
VariableDeclaration? indexVariable;
Expression readIndex = indexResult.expression;
Expression writeIndex;
if (isPureExpression(readIndex)) {
writeIndex = clonePureExpression(readIndex);
} else {
indexVariable = createVariable(readIndex, indexResult.inferredType);
readIndex = createVariableGet(indexVariable);
writeIndex = createVariableGet(indexVariable);
}
readIndex =
ensureAssignable(readIndexType, indexResult.inferredType, readIndex);
ExpressionInferenceResult readResult = _computeIndexGet(
node.readOffset,
readReceiver,
receiverType,
readTarget,
readIndex,
readIndexType,
MethodContravarianceCheckKind.none);
Expression read = readResult.expression;
DartType readType = readResult.inferredType;
VariableDeclaration? leftVariable;
Expression left;
if (node.forEffect) {
left = read;
} else if (node.forPostIncDec) {
leftVariable = createVariable(read, readType);
left = createVariableGet(leftVariable);
} else {
left = read;
}
ObjectAccessTarget writeTarget = node.setter != null
? new ExtensionAccessTarget(receiverType, node.setter!, null,
ClassMemberKind.Method, extensionTypeArguments)
: const ObjectAccessTarget.missing();
DartType writeIndexType = writeTarget.getIndexKeyType(this);
DartType valueType = writeTarget.getIndexSetValueType(this);
ExpressionInferenceResult binaryResult = _computeBinaryExpression(
node.binaryOffset,
valueType,
left,
readType,
node.binaryName,
node.rhs,
null);
writeIndex =
ensureAssignable(writeIndexType, indexResult.inferredType, writeIndex);
binaryResult = ensureAssignableResult(valueType, binaryResult,
fileOffset: node.fileOffset);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
VariableDeclaration? valueVariable;
Expression valueExpression;
if (node.forEffect || node.forPostIncDec) {
valueExpression = binary;
} else {
valueVariable = createVariable(binary, binaryType);
valueExpression = createVariableGet(valueVariable);
}
Expression write = _computeIndexSet(
node.writeOffset,
writeReceiver,
receiverType,
writeTarget,
writeIndex,
writeIndexType,
valueExpression,
valueType);
Expression replacement;
if (node.forEffect) {
assert(leftVariable == null);
assert(valueVariable == null);
// Encode `Extension(o)[a] += b` as:
//
// let receiverVariable = o in
// let indexVariable = a in
// receiverVariable.[]=(receiverVariable, o.[](indexVariable) + b)
//
replacement = write;
} else if (node.forPostIncDec) {
// Encode `Extension(o)[a]++` as:
//
// let receiverVariable = o in
// let indexVariable = a in
// let leftVariable = receiverVariable.[](indexVariable)
// let writeVariable =
// receiverVariable.[]=(indexVariable, leftVariable + 1) in
// leftVariable
//
assert(leftVariable != null);
assert(valueVariable == null);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
replacement = createLet(leftVariable!,
createLet(writeVariable, createVariableGet(leftVariable)));
} else {
// Encode `Extension(o)[a] += b` as:
//
// let receiverVariable = o in
// let indexVariable = a in
// let valueVariable = receiverVariable.[](indexVariable) + b
// let writeVariable =
// receiverVariable.[]=(indexVariable, valueVariable) in
// valueVariable
//
assert(leftVariable == null);
assert(valueVariable != null);
VariableDeclaration writeVariable =
createVariable(write, const VoidType());
replacement = createLet(valueVariable!,
createLet(writeVariable, createVariableGet(valueVariable)));
}
if (indexVariable != null) {
replacement = createLet(indexVariable, replacement);
}
if (receiverVariable != null) {
replacement = new Let(receiverVariable, replacement);
}
replacement.fileOffset = node.fileOffset;
return new ExpressionInferenceResult(
node.forPostIncDec ? readType : binaryType, replacement);
}
@override
ExpressionInferenceResult visitNullLiteral(
NullLiteral node, DartType typeContext) {
const NullType nullType = const NullType();
flowAnalysis.nullLiteral(node, nullType);
return new ExpressionInferenceResult(nullType, node);
}
@override
ExpressionInferenceResult visitLet(Let node, DartType typeContext) {
DartType variableType = node.variable.type;
ExpressionInferenceResult initializerResult = inferExpression(
node.variable.initializer!, variableType,
isVoidAllowed: true);
node.variable.initializer = initializerResult.expression
..parent = node.variable;
ExpressionInferenceResult bodyResult =
inferExpression(node.body, typeContext, isVoidAllowed: true);
node.body = bodyResult.expression..parent = node;
DartType inferredType = bodyResult.inferredType;
return new ExpressionInferenceResult(inferredType, node);
}
ExpressionInferenceResult visitPropertySet(
PropertySet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferNullAwareExpression(
node.receiver, const UnknownType(),
isVoidAllowed: false);
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
ObjectAccessTarget target = findInterfaceMember(
receiverType, node.name, node.fileOffset,
isSetter: true, instrumented: true, includeExtensionMethods: true);
if (target.isInstanceMember || target.isObjectMember) {
if (instrumentation != null && receiverType == const DynamicType()) {
instrumentation!.record(uriForInstrumentation, node.fileOffset,
'target', new InstrumentationValueForMember(target.member!));
}
}
DartType writeContext = target.getSetterType(this);
ExpressionInferenceResult rhsResult =
inferExpression(node.value, writeContext, isVoidAllowed: true);
rhsResult = ensureAssignableResult(writeContext, rhsResult,
fileOffset: node.fileOffset, isVoidAllowed: writeContext is VoidType);
Expression rhs = rhsResult.expression;
DartType rhsType = rhsResult.inferredType;
ExpressionInferenceResult replacementResult = _computePropertySet(
node.fileOffset, receiver, receiverType, node.name, target, rhs,
valueType: rhsType, forEffect: node.forEffect);
Expression replacement = replacementResult.expression;
DartType replacementType = replacementResult.inferredType;
return createNullAwareExpressionInferenceResult(
replacementType, replacement, nullAwareGuards);
}
ExpressionInferenceResult visitAugmentSuperSet(
AugmentSuperSet node, DartType typeContext) {
Member member = node.target;
if (member.isInstanceMember) {
Expression receiver = new ThisExpression()..fileOffset = node.fileOffset;
DartType receiverType = thisType!;
ObjectAccessTarget target = new ObjectAccessTarget.interfaceMember(
thisType!, member,
hasNonObjectMemberAccess: true);
if (target.isInstanceMember || target.isObjectMember) {
if (instrumentation != null && receiverType == const DynamicType()) {
instrumentation!.record(uriForInstrumentation, node.fileOffset,
'target', new InstrumentationValueForMember(target.member!));
}
}
DartType writeContext = target.getSetterType(this);
ExpressionInferenceResult rhsResult =
inferExpression(node.value, writeContext, isVoidAllowed: true);
rhsResult = ensureAssignableResult(writeContext, rhsResult,
fileOffset: node.fileOffset, isVoidAllowed: writeContext is VoidType);
Expression rhs = rhsResult.expression;
DartType rhsType = rhsResult.inferredType;
ExpressionInferenceResult replacementResult = _computePropertySet(
node.fileOffset, receiver, receiverType, member.name, target, rhs,
valueType: rhsType, forEffect: node.forEffect);
Expression replacement = replacementResult.expression;
DartType replacementType = replacementResult.inferredType;
return new ExpressionInferenceResult(replacementType, replacement);
} else {
// TODO(johnniwinther): Handle augmentation of field with inferred types.
TypeInferenceEngine.resolveInferenceNode(member, hierarchyBuilder);
DartType writeContext = member.setterType;
ExpressionInferenceResult rhsResult =
inferExpression(node.value, writeContext, isVoidAllowed: true);
rhsResult = ensureAssignableResult(writeContext, rhsResult,
fileOffset: node.fileOffset, isVoidAllowed: writeContext is VoidType);
Expression rhs = rhsResult.expression;
StaticSet result = new StaticSet(member, rhs)
..fileOffset = node.fileOffset;
DartType rhsType = rhsResult.inferredType;
return new ExpressionInferenceResult(rhsType, result);
}
}
ExpressionInferenceResult visitNullAwareIfNullSet(
NullAwareIfNullSet node, DartType typeContext) {
ExpressionInferenceResult receiverResult = inferNullAwareExpression(
node.receiver, const UnknownType(),
isVoidAllowed: false);
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
VariableDeclaration receiverVariable =
createVariable(receiver, receiverType);
NullAwareGuard nullAwareGuard = createNullAwareGuard(receiverVariable);
Expression readReceiver = createVariableGet(receiverVariable);
Expression writeReceiver = createVariableGet(receiverVariable);
DartType nonNullReceiverType = receiverType.toNonNull();
ExpressionInferenceResult readResult = _computePropertyGet(node.readOffset,
readReceiver, nonNullReceiverType, node.name, typeContext,
isThisReceiver: node.receiver is ThisExpression)
.expressionInferenceResult;
Expression read = readResult.expression;
DartType readType = readResult.inferredType;
flowAnalysis.ifNullExpression_rightBegin(read, readType);
VariableDeclaration? readVariable;
if (!node.forEffect) {
readVariable = createVariable(read, readType);
read = createVariableGet(readVariable);
}
ObjectAccessTarget writeTarget = findInterfaceMember(
nonNullReceiverType, node.name, node.writeOffset,
isSetter: true, includeExtensionMethods: true);
DartType valueType = writeTarget.getSetterType(this);
ExpressionInferenceResult valueResult =
inferExpression(node.value, valueType, isVoidAllowed: true);
valueResult = ensureAssignableResult(valueType, valueResult);
Expression value = valueResult.expression;
ExpressionInferenceResult writeResult = _computePropertySet(
node.writeOffset,
writeReceiver,
nonNullReceiverType,
node.name,
writeTarget,
value,
valueType: valueResult.inferredType,
forEffect: node.forEffect);
Expression write = writeResult.expression;
flowAnalysis.ifNullExpression_end();
DartType nonNullableReadType = readType.toNonNull();
DartType inferredType = _analyzeIfNullTypes(
nonNullableReadType: nonNullableReadType,
rhsType: valueResult.inferredType,
typeContext: typeContext);
Expression replacement;
if (node.forEffect) {
assert(readVariable == null);
// Encode `receiver?.name ??= value` as:
//
// let receiverVariable = receiver in
// receiverVariable == null ? null :
// (receiverVariable.name == null ?
// receiverVariable.name = value : null)
//
Expression readEqualsNull =
createEqualsNull(read, fileOffset: node.readOffset);
replacement = new ConditionalExpression(
readEqualsNull,
write,
new NullLiteral()..fileOffset = node.writeOffset,
computeNullable(inferredType))
..fileOffset = node.writeOffset;
} else {
// Encode `receiver?.name ??= value` as:
//
// let receiverVariable = receiver in
// receiverVariable == null ? null :
// (let readVariable = receiverVariable.name in
// readVariable == null ?
// receiverVariable.name = value : readVariable)
//
assert(readVariable != null);
Expression readEqualsNull =
createEqualsNull(read, fileOffset: receiverVariable.fileOffset);
VariableGet variableGet = createVariableGet(readVariable!);
if (isNonNullableByDefault && !identical(nonNullableReadType, readType)) {
variableGet.promotedType = nonNullableReadType;
}
ConditionalExpression condition = new ConditionalExpression(
readEqualsNull, write, variableGet, inferredType)
..fileOffset = receiverVariable.fileOffset;
replacement = createLet(readVariable, condition);
}
return createNullAwareExpressionInferenceResult(
inferredType, replacement, nullAwareGuards.prepend(nullAwareGuard));
}
ExpressionInferenceResult visitPropertyGet(
PropertyGet node, DartType typeContext) {
ExpressionInferenceResult receiverResult =
inferNullAwareExpression(node.receiver, const UnknownType());
Link<NullAwareGuard> nullAwareGuards = receiverResult.nullAwareGuards;
Expression receiver = receiverResult.nullAwareAction;
DartType receiverType = receiverResult.nullAwareActionType;
node.receiver = receiver..parent = node;
PropertyGetInferenceResult propertyGetInferenceResult = _computePropertyGet(
node.fileOffset, receiver, receiverType, node.name, typeContext,
isThisReceiver: node.receiver is ThisExpression, propertyGetNode: node);
ExpressionInferenceResult readResult =
propertyGetInferenceResult.expressionInferenceResult;
ExpressionInferenceResult expressionInferenceResult =
createNullAwareExpressionInferenceResult(
readResult.inferredType, readResult.expression, nullAwareGuards);
flowAnalysis.forwardExpression(
expressionInferenceResult.nullAwareAction, node);
return expressionInferenceResult;
}
@override
ExpressionInferenceResult visitRecordIndexGet(
RecordIndexGet node, DartType typeContext) {
ExpressionInferenceResult result =
inferNullAwareExpression(node.receiver, const UnknownType());
Link<NullAwareGuard> nullAwareGuards = result.nullAwareGuards;
Expression receiver = result.nullAwareAction;
DartType receiverType = result.nullAwareActionType;
node.receiver = receiver..parent = node;
if (receiverType is RecordType) {
if (node.index < receiverType.positional.length) {
DartType resultType = receiverType.positional[node.index];
return createNullAwareExpressionInferenceResult(
resultType, node, nullAwareGuards);
} else {
return wrapExpressionInferenceResultInProblem(
createNullAwareExpressionInferenceResult(
const InvalidType(), node, nullAwareGuards),
templateIndexOutOfBoundInRecordIndexGet.withArguments(
node.index,
receiverType.positional.length,
receiverType,
isNonNullableByDefault),
node.fileOffset,
noLength);
}
} else {
return wrapExpressionInferenceResultInProblem(
createNullAwareExpressionInferenceResult(
const InvalidType(), node, nullAwareGuards),
templateInternalProblemUnsupported.withArguments("RecordIndexGet"),
node.fileOffset,
noLength);
}
}
@override
ExpressionInferenceResult visitRecordNameGet(
RecordNameGet node, DartType typeContext) {
ExpressionInferenceResult result =
inferNullAwareExpression(node.receiver, const UnknownType());
Link<NullAwareGuard> nullAwareGuards = result.nullAwareGuards;
Expression receiver = result.nullAwareAction;
DartType receiverType = result.nullAwareActionType;
node.receiver = receiver..parent = node;
if (receiverType is RecordType) {
DartType? resultType;
for (NamedType namedType in receiverType.named) {
if (namedType.name == node.name) {
resultType = namedType.type;
break;
}
}
if (resultType != null) {
return createNullAwareExpressionInferenceResult(
resultType, node, nullAwareGuards);
} else {
return wrapExpressionInferenceResultInProblem(
createNullAwareExpressionInferenceResult(
const InvalidType(), node, nullAwareGuards),
templateNameNotFoundInRecordNameGet.withArguments(
node.name, receiverType, isNonNullableByDefault),
node.fileOffset,
noLength);
}
} else {
return wrapExpressionInferenceResultInProblem(
createNullAwareExpressionInferenceResult(
const InvalidType(), node, nullAwareGuards),
templateInternalProblemUnsupported.withArguments("RecordIndexGet"),
node.fileOffset,
noLength);
}
}
ExpressionInferenceResult visitAugmentSuperGet(
AugmentSuperGet node, DartType typeContext) {
Member member = node.target;
if (member.isInstanceMember) {
ObjectAccessTarget target = new ObjectAccessTarget.interfaceMember(
thisType!, member,
hasNonObjectMemberAccess: true);
Expression receiver = new ThisExpression()..fileOffset = node.fileOffset;
DartType receiverType = thisType!;
PropertyGetInferenceResult propertyGetInferenceResult =
_computePropertyGet(
node.fileOffset, receiver, receiverType, member.name, typeContext,
isThisReceiver: true, readTarget: target, propertyGetNode: node);
ExpressionInferenceResult readResult =
propertyGetInferenceResult.expressionInferenceResult;
return new ExpressionInferenceResult(
readResult.inferredType, readResult.expression);
} else {
// TODO(johnniwinther): Handle augmentation of field with inferred types.
TypeInferenceEngine.resolveInferenceNode(member, hierarchyBuilder);
DartType type = member.getterType;
if (member is Procedure && member.kind == ProcedureKind.Method) {
Expression tearOff = new StaticTearOff(node.target as Procedure)
..fileOffset = node.fileOffset;
return instantiateTearOff(type, typeContext, tearOff);
} else {
return new ExpressionInferenceResult(
type, new StaticGet(member)..fileOffset = node.fileOffset);
}
}
}
@override
InitializerInferenceResult visitRedirectingInitializer(
RedirectingInitializer node) {
ensureMemberType(node.target);
List<TypeParameter> classTypeParameters =
node.target.enclosingClass.typeParameters;
List<DartType> typeArguments = new List<DartType>.generate(
classTypeParameters.length,
(int i) => new TypeParameterType.withDefaultNullabilityForLibrary(
classTypeParameters[i], libraryBuilder.library),
growable: false);
// The redirecting initializer syntax doesn't include type arguments passed
// to the target constructor but we need to add them to the arguments before
// calling [inferInvocation]. These are removed again afterwards.
ArgumentsImpl.setNonInferrableArgumentTypes(
node.arguments as ArgumentsImpl, typeArguments);
FunctionType functionType = replaceReturnType(
node.target.function
.computeThisFunctionType(libraryBuilder.nonNullable),
coreTypes.thisInterfaceType(
node.target.enclosingClass, libraryBuilder.nonNullable));
InvocationInferenceResult inferenceResult = inferInvocation(
this,
const UnknownType(),
node.fileOffset,
functionType,
node.arguments as ArgumentsImpl,
skipTypeArgumentInference: true,
staticTarget: node.target);
ArgumentsImpl.removeNonInferrableArgumentTypes(
node.arguments as ArgumentsImpl);
return new InitializerInferenceResult.fromInvocationInferenceResult(
inferenceResult);
}
InitializerInferenceResult visitExtensionTypeRedirectingInitializer(
ExtensionTypeRedirectingInitializer node) {
ensureMemberType(node.target);
List<TypeParameter> constructorTypeParameters =
constructorDeclaration!.function.typeParameters;
List<DartType> typeArguments = new List<DartType>.generate(
constructorTypeParameters.length,
(int i) => new TypeParameterType.withDefaultNullabilityForLibrary(
constructorTypeParameters[i], libraryBuilder.library),
growable: false);
// The redirecting initializer syntax doesn't include type arguments passed
// to the target constructor but we need to add them to the arguments before
// calling [inferInvocation].
//
// Unlike in [visitRedirectingInitializer] we leave in the type arguments
// for the call to the target, since these are needed for the static
// invocation of the lowering.
ArgumentsImpl.setNonInferrableArgumentTypes(
node.arguments as ArgumentsImpl, typeArguments);
FunctionType functionType = node.target.function
.computeThisFunctionType(libraryBuilder.nonNullable);
InvocationInferenceResult inferenceResult = inferInvocation(
this,
const UnknownType(),
node.fileOffset,
functionType,
node.arguments as ArgumentsImpl,
skipTypeArgumentInference: true,
staticTarget: node.target);
return new InitializerInferenceResult.fromInvocationInferenceResult(
inferenceResult);
}
InitializerInferenceResult visitExtensionTypeRepresentationFieldInitializer(
ExtensionTypeRepresentationFieldInitializer node) {
DartType fieldType = node.field.getterType;
fieldType = constructorDeclaration!.substituteFieldType(fieldType);
ExpressionInferenceResult initializerResult =
inferExpression(node.value, fieldType);
Expression initializer = ensureAssignableResult(
fieldType, initializerResult,
fileOffset: node.fileOffset)
.expression;
node.value = initializer..parent = node;
return const SuccessfulInitializerInferenceResult();
}
@override
ExpressionInferenceResult visitRethrow(Rethrow node, DartType typeContext) {
flowAnalysis.handleExit();
return new ExpressionInferenceResult(
isNonNullableByDefault
? const NeverType.nonNullable()
: const NeverType.legacy(),
node);
}
@override
StatementInferenceResult visitReturnStatement(
covariant ReturnStatementImpl node) {
DartType typeContext = closureContext.returnContext;
DartType inferredType;
if (node.expression != null) {
ExpressionInferenceResult expressionResult =
inferExpression(node.expression!, typeContext, isVoidAllowed: true);
node.expression = expressionResult.expression..parent = node;
inferredType = expressionResult.inferredType;
} else {
inferredType = const NullType();
}
closureContext.handleReturn(node, inferredType, node.isArrow);
flowAnalysis.handleExit();
return const StatementInferenceResult();
}
@override
ExpressionInferenceResult visitSetLiteral(
SetLiteral node, DartType typeContext) {
Class setClass = coreTypes.setClass;
InterfaceType setType =
coreTypes.thisInterfaceType(setClass, libraryBuilder.nonNullable);
List<DartType>? inferredTypes;
DartType inferredTypeArgument;
bool inferenceNeeded = node.typeArgument is ImplicitTypeArgument;
List<DartType> formalTypes = [];
List<DartType> actualTypes = [];
Map<TreeNode, DartType> inferredSpreadTypes =
new Map<TreeNode, DartType>.identity();
Map<Expression, DartType> inferredConditionTypes =
new Map<Expression, DartType>.identity();
TypeConstraintGatherer? gatherer;
FreshStructuralParametersFromTypeParameters freshTypeParameters =
getFreshStructuralParametersFromTypeParameters(setClass.typeParameters);
List<StructuralParameter> typeParametersToInfer =
freshTypeParameters.freshTypeParameters;
setType = freshTypeParameters.substitute(setType) as InterfaceType;
if (inferenceNeeded) {
gatherer = typeSchemaEnvironment.setupGenericTypeInference(
setType, typeParametersToInfer, typeContext,
isNonNullableByDefault: isNonNullableByDefault,
isConst: node.isConst,
typeOperations: operations,
inferenceResultForTesting: dataForTesting?.typeInferenceResult,
treeNodeForTesting: node);
inferredTypes = typeSchemaEnvironment.choosePreliminaryTypes(
gatherer, typeParametersToInfer, null,
isNonNullableByDefault: isNonNullableByDefault);
inferredTypeArgument = inferredTypes[0];
} else {
inferredTypeArgument = node.typeArgument;
}
for (int index = 0; index < node.expressions.length; ++index) {
ExpressionInferenceResult result = inferElement(node.expressions[index],
inferredTypeArgument, inferredSpreadTypes, inferredConditionTypes);
node.expressions[index] = result.expression..parent = node;
actualTypes.add(result.inferredType);
if (inferenceNeeded) {
formalTypes.add(setType.typeArguments[0]);
}
}
if (inferenceNeeded) {
gatherer!.constrainArguments(formalTypes, actualTypes,
treeNodeForTesting: node);
inferredTypes = typeSchemaEnvironment.chooseFinalTypes(
gatherer, typeParametersToInfer, inferredTypes!,
isNonNullableByDefault: isNonNullableByDefault);
if (dataForTesting != null) {
dataForTesting!.typeInferenceResult.inferredTypeArguments[node] =
inferredTypes;
}
inferredTypeArgument = inferredTypes[0];
instrumentation?.record(
uriForInstrumentation,
node.fileOffset,
'typeArgs',
new InstrumentationValueForTypeArgs([inferredTypeArgument]));
node.typeArgument = inferredTypeArgument;
}
for (int i = 0; i < node.expressions.length; i++) {
checkElement(node.expressions[i], node, node.typeArgument,
inferredSpreadTypes, inferredConditionTypes);
}
DartType inferredType = new InterfaceType(
setClass, libraryBuilder.nonNullable, [inferredTypeArgument]);
if (inferenceNeeded) {
if (!libraryBuilder.libraryFeatures.genericMetadata.isEnabled) {
checkGenericFunctionTypeArgument(node.typeArgument, node.fileOffset);
}
}
Expression result = _translateSetLiteral(node);
return new ExpressionInferenceResult(inferredType, result);
}
/// Creates a lowering for [node] for targets that don't support the
/// [SetLiteral] node.
Expression _lowerSetLiteral(SetLiteral node) {
if (libraryBuilder.loader.target.backendTarget.supportsSetLiterals) {
return node;
}
if (node.isConst) {
// Const set literals are transformed in the constant evaluator.
return node;
}
// Create the set: Set<E> setVar = new Set<E>();
InterfaceType receiverType;
VariableDeclaration setVar = new VariableDeclaration.forValue(
new StaticInvocation(
engine.setFactory, new Arguments([], types: [node.typeArgument])),
type: receiverType = new InterfaceType(coreTypes.setClass,
libraryBuilder.nonNullable, [node.typeArgument]));
// Now create a list of all statements needed.
List<Statement> statements = [setVar];
for (int i = 0; i < node.expressions.length; i++) {
Expression entry = node.expressions[i];
DartType functionType = Substitution.fromInterfaceType(receiverType)
.substituteType(engine.setAddMethodFunctionType);
if (!isNonNullableByDefault) {
functionType = legacyErasure(functionType);
}
Expression methodInvocation = new InstanceInvocation(
InstanceAccessKind.Instance,
new VariableGet(setVar),
new Name("add"),
new Arguments([entry]),
functionType: functionType as FunctionType,
interfaceTarget: engine.setAddMethod)
..fileOffset = entry.fileOffset
..isInvariant = true;
statements.add(new ExpressionStatement(methodInvocation)
..fileOffset = methodInvocation.fileOffset);
}
// Finally, return a BlockExpression with the statements, having the value
// of the (now created) set.
return new BlockExpression(new Block(statements), new VariableGet(setVar))
..fileOffset = node.fileOffset;
}
@override
ExpressionInferenceResult visitStaticSet(
StaticSet node, DartType typeContext) {
Member writeMember = node.target;
TypeInferenceEngine.resolveInferenceNode(writeMember, hierarchyBuilder);
DartType writeContext = writeMember.setterType;
ExpressionInferenceResult rhsResult =
inferExpression(node.value, writeContext, isVoidAllowed: true);
rhsResult = ensureAssignableResult(writeContext, rhsResult,
fileOffset: node.fileOffset, isVoidAllowed: writeContext is VoidType);
Expression rhs = rhsResult.expression;
node.value = rhs..parent = node;
DartType rhsType = rhsResult.inferredType;
return new ExpressionInferenceResult(rhsType, node);
}
@override
ExpressionInferenceResult visitStaticGet(
StaticGet node, DartType typeContext) {
Member target = node.target;
TypeInferenceEngine.resolveInferenceNode(target, hierarchyBuilder);
DartType type = target.getterType;
if (!isNonNullableByDefault) {
type = legacyErasure(type);
}
if (target is Procedure && target.kind == ProcedureKind.Method) {
Expression tearOff = new StaticTearOff(node.target as Procedure)
..fileOffset = node.fileOffset;
return instantiateTearOff(type, typeContext, tearOff);
} else {
return new ExpressionInferenceResult(type, node);
}
}
@override
ExpressionInferenceResult visitStaticInvocation(
StaticInvocation node, DartType typeContext) {
FunctionType calleeType =
node.target.function.computeFunctionType(libraryBuilder.nonNullable);
TypeArgumentsInfo typeArgumentsInfo = getTypeArgumentsInfo(node.arguments);
InvocationInferenceResult result = inferInvocation(this, typeContext,
node.fileOffset, calleeType, node.arguments as ArgumentsImpl,
staticTarget: node.target);
libraryBuilder.checkBoundsInStaticInvocation(
node, typeSchemaEnvironment, helper.uri, typeArgumentsInfo);
return new ExpressionInferenceResult(
result.inferredType, result.applyResult(node));
}
@override
ExpressionInferenceResult visitStringConcatenation(
StringConcatenation node, DartType typeContext) {
for (int index = 0; index < node.expressions.length; index++) {
ExpressionInferenceResult result = inferExpression(
node.expressions[index], const UnknownType(),
isVoidAllowed: false);
node.expressions[index] = result.expression..parent = node;
}
return new ExpressionInferenceResult(
coreTypes.stringRawType(libraryBuilder.nonNullable), node);
}
@override
ExpressionInferenceResult visitStringLiteral(
StringLiteral node, DartType typeContext) {
return new ExpressionInferenceResult(
coreTypes.stringRawType(libraryBuilder.nonNullable), node);
}
@override
InitializerInferenceResult visitSuperInitializer(SuperInitializer node) {
ensureMemberType(node.target);
Supertype asSuperClass = hierarchyBuilder.getClassAsInstanceOf(
thisType!.classNode, node.target.enclosingClass)!;
FunctionType targetType = node.target.function
.computeThisFunctionType(libraryBuilder.nonNullable);
FunctionType instantiatedTargetType = FunctionTypeInstantiator.instantiate(
targetType, asSuperClass.typeArguments);
FunctionType functionType =
replaceReturnType(instantiatedTargetType, thisType!);
InvocationInferenceResult inferenceResult = inferInvocation(
this,
const UnknownType(),
node.fileOffset,
functionType,
node.arguments as ArgumentsImpl,
skipTypeArgumentInference: true,
staticTarget: node.target);
return new InitializerInferenceResult.fromInvocationInferenceResult(
inferenceResult);
}
@override
ExpressionInferenceResult visitAbstractSuperMethodInvocation(
AbstractSuperMethodInvocation node, DartType typeContext) {
instrumentation?.record(uriForInstrumentation, node.fileOffset, 'target',
new InstrumentationValueForMember(node.interfaceTarget));
return inferSuperMethodInvocation(this, node, node.name,
node.arguments as ArgumentsImpl, typeContext, node.interfaceTarget);
}
@override
ExpressionInferenceResult visitSuperMethodInvocation(
SuperMethodInvocation node, DartType typeContext) {
instrumentation?.record(uriForInstrumentation, node.fileOffset, 'target',
new InstrumentationValueForMember(node.interfaceTarget));
return inferSuperMethodInvocation(this, node, node.name,
node.arguments as ArgumentsImpl, typeContext, node.interfaceTarget);
}
@override
ExpressionInferenceResult visitAbstractSuperPropertyGet(
AbstractSuperPropertyGet node, DartType typeContext) {
instrumentation?.record(uriForInstrumentation, node.fileOffset, 'target',
new InstrumentationValueForMember(node.interfaceTarget));
return inferSuperPropertyGet(
node, node.name, typeContext, node.interfaceTarget);
}
@override
ExpressionInferenceResult visitSuperPropertyGet(
SuperPropertyGet node, DartType typeContext) {
instrumentation?.record(uriForInstrumentation, node.fileOffset, 'target',
new InstrumentationValueForMember(node.interfaceTarget));
return inferSuperPropertyGet(
node, node.name, typeContext, node.interfaceTarget);
}
@override
ExpressionInferenceResult visitAbstractSuperPropertySet(
AbstractSuperPropertySet node, DartType typeContext) {
ObjectAccessTarget writeTarget = new ObjectAccessTarget.interfaceMember(
thisType!, node.interfaceTarget,
hasNonObjectMemberAccess: true);
DartType writeContext = writeTarget.getSetterType(this);
writeContext = computeTypeFromSuperClass(
node.interfaceTarget.enclosingClass!, writeContext);
ExpressionInferenceResult rhsResult =
inferExpression(node.value, writeContext, isVoidAllowed: true);
rhsResult = ensureAssignableResult(writeContext, rhsResult,
fileOffset: node.fileOffset, isVoidAllowed: writeContext is VoidType);
Expression rhs = rhsResult.expression;
node.value = rhs..parent = node;
return new ExpressionInferenceResult(rhsResult.inferredType, node);
}
@override
ExpressionInferenceResult visitSuperPropertySet(
SuperPropertySet node, DartType typeContext) {
ObjectAccessTarget writeTarget = thisType!.classNode.isMixinDeclaration
? new ObjectAccessTarget.interfaceMember(
thisType!, node.interfaceTarget,
hasNonObjectMemberAccess: true)
: new ObjectAccessTarget.superMember(thisType!, node.interfaceTarget);
DartType writeContext = writeTarget.getSetterType(this);
writeContext = computeTypeFromSuperClass(
node.interfaceTarget.enclosingClass!, writeContext);
ExpressionInferenceResult rhsResult =
inferExpression(node.value, writeContext, isVoidAllowed: true);
rhsResult = ensureAssignableResult(writeContext, rhsResult,
fileOffset: node.fileOffset, isVoidAllowed: writeContext is VoidType);
Expression rhs = rhsResult.expression;
node.value = rhs..parent = node;
return new ExpressionInferenceResult(rhsResult.inferredType, node);
}
@override
ExpressionInferenceResult visitSwitchExpression(
SwitchExpression node, DartType typeContext) {
Set<Field?>? previousEnumFields = _enumFields;
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
SwitchExpressionResult<DartType, InvalidExpression> analysisResult =
analyzeSwitchExpression(
node, node.expression, node.cases.length, typeContext);
DartType valueType = analysisResult.type;
node.staticType = valueType;
assert(checkStack(node, stackBase, [
/* scrutineeType = */ ValueKinds.DartType,
/* scrutinee = */ ValueKinds.Expression,
]));
DartType scrutineeType = popRewrite() as DartType;
node.expressionType = scrutineeType;
assert(checkStack(node, stackBase, [
/* scrutinee = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite();
if (rewrite != null && !identical(node.expression, rewrite)) {
node.expression = rewrite as Expression..parent = node;
}
for (int caseIndex = 0; caseIndex < node.cases.length; caseIndex++) {
SwitchExpressionCase switchCase = node.cases[caseIndex];
PatternGuard patternGuard = switchCase.patternGuard;
InvalidExpression? guardError =
analysisResult.nonBooleanGuardErrors?[caseIndex];
if (guardError != null) {
patternGuard.guard = guardError..parent = patternGuard;
} else if (patternGuard.guard != null) {
if (analysisResult.guardTypes![caseIndex] is DynamicType) {
patternGuard.guard = _createImplicitAs(patternGuard.guard!.fileOffset,
patternGuard.guard!, coreTypes.boolNonNullableRawType)
..parent = patternGuard;
}
}
}
_enumFields = previousEnumFields;
assert(checkStack(node, stackBase, [/*empty*/]));
return new ExpressionInferenceResult(valueType, node);
}
@override
StatementInferenceResult visitSwitchStatement(SwitchStatement node) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
Set<Field?>? previousEnumFields = _enumFields;
Expression expression = node.expression;
SwitchStatementTypeAnalysisResult<DartType, InvalidExpression>
analysisResult =
analyzeSwitchStatement(node, expression, node.cases.length);
node.expressionType = analysisResult.scrutineeType;
assert(checkStack(node, stackBase, [
/* cases = */ ...repeatedKind(ValueKinds.SwitchCase, node.cases.length),
/* scrutinee type = */ ValueKinds.DartType,
/* scrutinee = */ ValueKinds.Expression,
]));
for (int i = node.cases.length - 1; i >= 0; i--) {
popRewrite(); // StatementCase
}
// Note that a switch statement with a `default` clause is always considered
// exhaustive, but the kernel format also keeps track of whether the switch
// statement is "explicitly exhaustive", meaning that it has a `case` clause
// for every possible enum value. It is only necessary to set this flag if
// the switch doesn't have a `default` clause.
if (!analysisResult.hasDefault) {
node.isExplicitlyExhaustive = analysisResult.isExhaustive;
}
_enumFields = previousEnumFields;
assert(checkStack(node, stackBase, [
/* scrutineeType = */ ValueKinds.DartType,
/* scrutinee = */ ValueKinds.Expression,
]));
popRewrite(); // Scrutinee type.
assert(checkStack(node, stackBase, [
/* scrutinee = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite();
if (!identical(expression, rewrite)) {
expression = rewrite as Expression;
node.expression = expression..parent = node;
}
Statement? replacement;
if (analysisResult.isExhaustive &&
!analysisResult.hasDefault &&
shouldThrowUnsoundnessException) {
if (!analysisResult.lastCaseTerminates) {
LabeledStatement breakTarget;
if (node.parent is LabeledStatement) {
breakTarget = node.parent as LabeledStatement;
} else {
replacement = breakTarget = new LabeledStatement(node);
}
SwitchCase lastCase = node.cases.last;
Statement body = lastCase.body;
if (body is Block) {
body.statements.add(new BreakStatementImpl(isContinue: false)
..target = breakTarget
..targetStatement = node
..fileOffset = node.fileOffset);
}
}
node.cases.add(new SwitchCase(
[],
[],
_createExpressionStatement(createReachabilityError(
node.fileOffset, messageNeverReachableSwitchDefaultError)),
isDefault: true)
..fileOffset = node.fileOffset
..parent = node);
}
assert(checkStack(node, stackBase, [/*empty*/]));
return replacement != null
? new StatementInferenceResult.single(replacement)
: const StatementInferenceResult();
}
@override
StatementInferenceResult visitPatternSwitchStatement(
PatternSwitchStatement node) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
SwitchStatementTypeAnalysisResult<DartType, InvalidExpression>
analysisResult =
analyzeSwitchStatement(node, node.expression, node.cases.length);
node.lastCaseTerminates = analysisResult.lastCaseTerminates;
assert(checkStack(node, stackBase, [
/* cases = */ ...repeatedKind(ValueKinds.SwitchCase, node.cases.length),
/* scrutinee type = */ ValueKinds.DartType,
/* scrutinee = */ ValueKinds.Expression,
]));
node.expressionType = analysisResult.scrutineeType;
for (int i = node.cases.length - 1; i >= 0; i--) {
Object? rewrite = popRewrite();
if (!identical(rewrite, node.cases[i])) {
node.cases[i] = (rewrite as PatternSwitchCase)..parent = node;
}
}
assert(checkStack(node, stackBase, [
/* scrutinee type = */ ValueKinds.DartType,
/* scrutinee = */ ValueKinds.Expression,
]));
popRewrite(); // Scrutinee type.
assert(checkStack(node, stackBase, [
/* scrutinee = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite();
if (!identical(node.expression, rewrite)) {
node.expression = rewrite as Expression..parent = node;
}
for (int caseIndex = 0; caseIndex < node.cases.length; caseIndex++) {
PatternSwitchCase switchCase = node.cases[caseIndex];
List<VariableDeclaration> jointVariablesNotInAll = [];
for (int headIndex = 0;
headIndex < switchCase.patternGuards.length;
headIndex++) {
PatternGuard patternGuard = switchCase.patternGuards[headIndex];
Pattern pattern = patternGuard.pattern;
InvalidExpression? guardError =
analysisResult.nonBooleanGuardErrors?[caseIndex]?[headIndex];
if (guardError != null) {
patternGuard.guard = guardError..parent = patternGuard;
} else if (patternGuard.guard != null) {
if (analysisResult.guardTypes![caseIndex]![headIndex]
is DynamicType) {
patternGuard.guard = _createImplicitAs(
patternGuard.guard!.fileOffset,
patternGuard.guard!,
coreTypes.boolNonNullableRawType)
..parent = patternGuard;
}
}
Map<String, DartType> inferredVariableTypes = {
for (VariableDeclaration variable in pattern.declaredVariables)
variable.name!: variable.type
};
if (headIndex == 0) {
for (VariableDeclaration jointVariable in switchCase.jointVariables) {
DartType? inferredType = inferredVariableTypes[jointVariable.name!];
if (inferredType != null) {
jointVariable.type = inferredType;
} else {
jointVariable.type = const InvalidType();
jointVariablesNotInAll.add(jointVariable);
}
}
} else {
for (int i = 0; i < switchCase.jointVariables.length; ++i) {
VariableDeclaration jointVariable = switchCase.jointVariables[i];
// The error on joint variables not present in all case heads is
// reported in BodyBuilder.
DartType? inferredType = inferredVariableTypes[jointVariable.name!];
if (!jointVariablesNotInAll.contains(jointVariable) &&
inferredType != null &&
jointVariable.type != inferredType) {
jointVariable.initializer = helper.buildProblem(
templateJointPatternVariablesMismatch
.withArguments(jointVariable.name!),
switchCase.jointVariableFirstUseOffsets?[i] ??
jointVariable.fileOffset,
noLength)
..parent = jointVariable;
}
}
}
}
}
return const StatementInferenceResult();
}
@override
ExpressionInferenceResult visitSymbolLiteral(
SymbolLiteral node, DartType typeContext) {
DartType inferredType = coreTypes.symbolRawType(libraryBuilder.nonNullable);
return new ExpressionInferenceResult(inferredType, node);
}
@override
ExpressionInferenceResult visitThisExpression(
ThisExpression node, DartType typeContext) {
flowAnalysis.thisOrSuper(node, thisType!, isSuper: false);
return new ExpressionInferenceResult(thisType!, node);
}
@override
ExpressionInferenceResult visitThrow(Throw node, DartType typeContext) {
ExpressionInferenceResult expressionResult = inferExpression(
node.expression, const UnknownType(),
isVoidAllowed: false);
node.expression = expressionResult.expression..parent = node;
flowAnalysis.handleExit();
if (isNonNullableByDefault) {
if (!isAssignable(typeSchemaEnvironment.objectNonNullableRawType,
expressionResult.inferredType)) {
return new ExpressionInferenceResult(
const DynamicType(),
helper.buildProblem(
templateThrowingNotAssignableToObjectError.withArguments(
expressionResult.inferredType, true),
node.expression.fileOffset,
noLength));
}
}
if (isNonNullableByDefault &&
expressionResult.inferredType.isPotentiallyNullable) {
node.expression =
new AsExpression(node.expression, coreTypes.objectNonNullableRawType)
..isTypeError = true
..isForNonNullableByDefault = true
..fileOffset = node.expression.fileOffset
..parent = node;
}
// Return BottomType in legacy mode for compatibility.
return new ExpressionInferenceResult(
isNonNullableByDefault
? const NeverType.nonNullable()
: const NullType(),
node);
}
void visitCatch(Catch node) {
StatementInferenceResult bodyResult = inferStatement(node.body);
if (bodyResult.hasChanged) {
node.body = bodyResult.statement..parent = node;
}
}
StatementInferenceResult visitTryStatement(TryStatement node) {
bool oldInTryOrLocalFunction = _inTryOrLocalFunction;
_inTryOrLocalFunction = true;
if (node.finallyBlock != null) {
flowAnalysis.tryFinallyStatement_bodyBegin();
}
Statement tryBodyWithAssignedInfo = node.tryBlock;
if (node.catchBlocks.isNotEmpty) {
flowAnalysis.tryCatchStatement_bodyBegin();
}
StatementInferenceResult tryBlockResult = inferStatement(node.tryBlock);
if (node.catchBlocks.isNotEmpty) {
flowAnalysis.tryCatchStatement_bodyEnd(tryBodyWithAssignedInfo);
for (Catch catchBlock in node.catchBlocks) {
flowAnalysis.tryCatchStatement_catchBegin(
catchBlock.exception, catchBlock.stackTrace);
visitCatch(catchBlock);
flowAnalysis.tryCatchStatement_catchEnd();
}
flowAnalysis.tryCatchStatement_end();
}
StatementInferenceResult? finalizerResult;
if (node.finallyBlock != null) {
// If a try statement has no catch blocks, the finally block uses the
// assigned variables from the try block in [tryBodyWithAssignedInfo],
// otherwise it uses the assigned variables for the
flowAnalysis.tryFinallyStatement_finallyBegin(
node.catchBlocks.isNotEmpty ? node : tryBodyWithAssignedInfo);
finalizerResult = inferStatement(node.finallyBlock!);
flowAnalysis.tryFinallyStatement_end();
}
Statement result =
tryBlockResult.hasChanged ? tryBlockResult.statement : node.tryBlock;
if (node.catchBlocks.isNotEmpty) {
result = new TryCatch(result, node.catchBlocks)
..fileOffset = node.fileOffset;
}
if (node.finallyBlock != null) {
result = new TryFinally(
result,
finalizerResult!.hasChanged
? finalizerResult.statement
: node.finallyBlock!)
..fileOffset = node.fileOffset;
}
libraryBuilder.loader.dataForTesting?.registerAlias(node, result);
_inTryOrLocalFunction = oldInTryOrLocalFunction;
return new StatementInferenceResult.single(result);
}
@override
ExpressionInferenceResult visitTypeLiteral(
TypeLiteral node, DartType typeContext) {
DartType inferredType = coreTypes.typeRawType(libraryBuilder.nonNullable);
return new ExpressionInferenceResult(inferredType, node);
}
@override
ExpressionInferenceResult visitVariableSet(
VariableSet node, DartType typeContext) {
VariableDeclarationImpl variable = node.variable as VariableDeclarationImpl;
bool isDefinitelyAssigned = false;
bool isDefinitelyUnassigned = false;
if (isNonNullableByDefault) {
isDefinitelyAssigned = flowAnalysis.isAssigned(variable);
isDefinitelyUnassigned = flowAnalysis.isUnassigned(variable);
}
DartType declaredOrInferredType = variable.lateType ?? variable.type;
DartType? promotedType;
if (isNonNullableByDefault) {
promotedType = flowAnalysis.promotedType(variable);
}
ExpressionInferenceResult rhsResult = inferExpression(
node.value, promotedType ?? declaredOrInferredType,
isVoidAllowed: true);
rhsResult = ensureAssignableResult(declaredOrInferredType, rhsResult,
fileOffset: node.fileOffset,
isVoidAllowed: declaredOrInferredType is VoidType);
Expression rhs = rhsResult.expression;
flowAnalysis.write(
node, variable, rhsResult.inferredType, rhsResult.expression);
DartType resultType = rhsResult.inferredType;
Expression resultExpression;
if (variable.lateSetter != null) {
resultExpression = new LocalFunctionInvocation(variable.lateSetter!,
new Arguments(<Expression>[rhs])..fileOffset = node.fileOffset,
functionType: variable.lateSetter!.type as FunctionType)
..fileOffset = node.fileOffset;
// Future calls to flow analysis will be using `resultExpression` to refer
// to the variable set, so instruct flow analysis to forward the
// expression information.
flowAnalysis.forwardExpression(resultExpression, node);
} else {
node.value = rhs..parent = node;
resultExpression = node;
}
if (isNonNullableByDefault) {
// Synthetic variables, local functions, and variables with
// invalid types aren't checked.
if (variable.name != null &&
!variable.isLocalFunction &&
declaredOrInferredType is! InvalidType) {
if ((variable.isLate && variable.isFinal) ||
variable.isLateFinalWithoutInitializer) {
if (isDefinitelyAssigned) {
return new ExpressionInferenceResult(
resultType,
helper.wrapInProblem(
resultExpression,
templateLateDefinitelyAssignedError
.withArguments(node.variable.name!),
node.fileOffset,
node.variable.name!.length));
}
} else if (variable.isStaticLate) {
if (!isDefinitelyUnassigned) {
return new ExpressionInferenceResult(
resultType,
helper.wrapInProblem(
resultExpression,
templateFinalPossiblyAssignedError
.withArguments(node.variable.name!),
node.fileOffset,
node.variable.name!.length));
}
}
}
}
return new ExpressionInferenceResult(resultType, resultExpression);
}
@override
StatementInferenceResult visitVariableDeclaration(
covariant VariableDeclarationImpl node) {
DartType declaredType =
node.isImplicitlyTyped ? const UnknownType() : node.type;
DartType inferredType;
ExpressionInferenceResult? initializerResult;
if (node.initializer != null) {
if (node.isLate && node.hasDeclaredInitializer) {
flowAnalysis.lateInitializer_begin(node);
}
initializerResult =
inferExpression(node.initializer!, declaredType, isVoidAllowed: true);
if (node.isLate && node.hasDeclaredInitializer) {
flowAnalysis.lateInitializer_end();
}
inferredType = inferDeclarationType(initializerResult.inferredType,
forSyntheticVariable: node.name == null);
} else {
inferredType = const DynamicType();
}
if (node.isImplicitlyTyped) {
instrumentation?.record(uriForInstrumentation, node.fileOffset, 'type',
new InstrumentationValueForType(inferredType));
if (dataForTesting != null) {
dataForTesting!.typeInferenceResult.inferredVariableTypes[node] =
inferredType;
}
node.type = inferredType;
}
flowAnalysis.declare(node, node.type,
initialized: node.hasDeclaredInitializer);
if (initializerResult != null) {
DartType initializerType = initializerResult.inferredType;
flowAnalysis.initialize(
node, initializerType, initializerResult.expression,
isFinal: node.isFinal,
isLate: node.isLate,
isImplicitlyTyped: node.isImplicitlyTyped);
initializerResult = ensureAssignableResult(node.type, initializerResult,
fileOffset: node.fileOffset, isVoidAllowed: node.type is VoidType);
Expression initializer = initializerResult.expression;
node.initializer = initializer..parent = node;
}
if (node.isLate &&
libraryBuilder.loader.target.backendTarget.isLateLocalLoweringEnabled(
hasInitializer: node.hasDeclaredInitializer,
isFinal: node.isFinal,
isPotentiallyNullable: node.type.isPotentiallyNullable)) {
int fileOffset = node.fileOffset;
List<Statement> result = <Statement>[];
result.add(node);
late_lowering.IsSetEncoding isSetEncoding =
late_lowering.computeIsSetEncoding(
node.type, late_lowering.computeIsSetStrategy(libraryBuilder));
VariableDeclaration? isSetVariable;
if (isSetEncoding == late_lowering.IsSetEncoding.useIsSetField) {
isSetVariable = new VariableDeclaration(
late_lowering.computeLateLocalIsSetName(node.name!),
initializer: new BoolLiteral(false)..fileOffset = fileOffset,
type: coreTypes.boolRawType(libraryBuilder.nonNullable),
isLowered: true)
..fileOffset = fileOffset;
result.add(isSetVariable);
}
Expression createVariableRead({bool needsPromotion = false}) {
if (needsPromotion) {
return new VariableGet(node, node.type)..fileOffset = fileOffset;
} else {
return new VariableGet(node)..fileOffset = fileOffset;
}
}
Expression createIsSetRead() =>
new VariableGet(isSetVariable!)..fileOffset = fileOffset;
Expression createVariableWrite(Expression value) =>
new VariableSet(node, value);
Expression createIsSetWrite(Expression value) =>
new VariableSet(isSetVariable!, value);
VariableDeclaration getVariable = new VariableDeclaration(
late_lowering.computeLateLocalGetterName(node.name!),
isLowered: true)
..fileOffset = fileOffset;
FunctionDeclaration getter = new FunctionDeclaration(
getVariable,
new FunctionNode(
node.initializer == null
? late_lowering.createGetterBodyWithoutInitializer(
coreTypes, fileOffset, node.name!, node.type,
createVariableRead: createVariableRead,
createIsSetRead: createIsSetRead,
isSetEncoding: isSetEncoding,
forField: false)
: (node.isFinal
? late_lowering.createGetterWithInitializerWithRecheck(
coreTypes,
fileOffset,
node.name!,
node.type,
node.initializer!,
createVariableRead: createVariableRead,
createVariableWrite: createVariableWrite,
createIsSetRead: createIsSetRead,
createIsSetWrite: createIsSetWrite,
isSetEncoding: isSetEncoding,
forField: false)
: late_lowering.createGetterWithInitializer(coreTypes,
fileOffset, node.name!, node.type, node.initializer!,
createVariableRead: createVariableRead,
createVariableWrite: createVariableWrite,
createIsSetRead: createIsSetRead,
createIsSetWrite: createIsSetWrite,
isSetEncoding: isSetEncoding)),
returnType: node.type))
..fileOffset = fileOffset;
getVariable.type =
getter.function.computeFunctionType(libraryBuilder.nonNullable);
node.lateGetter = getVariable;
result.add(getter);
if (!node.isFinal || node.initializer == null) {
node.isLateFinalWithoutInitializer =
node.isFinal && node.initializer == null;
VariableDeclaration setVariable = new VariableDeclaration(
late_lowering.computeLateLocalSetterName(node.name!),
isLowered: true)
..fileOffset = fileOffset;
VariableDeclaration setterParameter =
new VariableDeclaration("${node.name}#param", type: node.type)
..fileOffset = fileOffset;
FunctionDeclaration setter = new FunctionDeclaration(
setVariable,
new FunctionNode(
node.isFinal
? late_lowering.createSetterBodyFinal(coreTypes,
fileOffset, node.name!, setterParameter, node.type,
shouldReturnValue: true,
createVariableRead: createVariableRead,
createVariableWrite: createVariableWrite,
createIsSetRead: createIsSetRead,
createIsSetWrite: createIsSetWrite,
isSetEncoding: isSetEncoding,
forField: false)
: late_lowering.createSetterBody(coreTypes, fileOffset,
node.name!, setterParameter, node.type,
shouldReturnValue: true,
createVariableWrite: createVariableWrite,
createIsSetWrite: createIsSetWrite,
isSetEncoding: isSetEncoding)
..fileOffset = fileOffset,
positionalParameters: <VariableDeclaration>[
setterParameter
]))
// TODO(johnniwinther): Reinsert the file offset when the vm doesn't
// use it for function declaration identity.
/*..fileOffset = fileOffset*/;
setVariable.type =
setter.function.computeFunctionType(libraryBuilder.nonNullable);
node.lateSetter = setVariable;
result.add(setter);
}
node.isLate = false;
node.lateType = node.type;
if (isSetEncoding == late_lowering.IsSetEncoding.useSentinel) {
node.initializer = new StaticInvocation(coreTypes.createSentinelMethod,
new Arguments([], types: [node.type])..fileOffset = fileOffset)
..fileOffset = fileOffset
..parent = node;
} else {
node.initializer = null;
}
node.type = computeNullable(node.type);
node.lateName = node.name;
node.isLowered = true;
node.name = late_lowering.computeLateLocalName(node.name!);
return new StatementInferenceResult.multiple(node.fileOffset, result);
}
return const StatementInferenceResult();
}
@override
StatementInferenceResult visitPatternVariableDeclaration(
PatternVariableDeclaration node) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
PatternVariableDeclarationAnalysisResult<DartType, DartType>
analysisResult = analyzePatternVariableDeclaration(
node, node.pattern, node.initializer,
isFinal: node.isFinal);
node.matchedValueType = analysisResult.initializerType;
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
/* initializer = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite();
if (!identical(rewrite, node.pattern)) {
node.pattern = rewrite as Pattern..parent = node;
}
assert(checkStack(node, stackBase, [
/* initializer = */ ValueKinds.Expression,
]));
rewrite = popRewrite();
if (!identical(node.initializer, rewrite)) {
node.initializer = rewrite as Expression..parent = node;
}
return const StatementInferenceResult();
}
@override
ExpressionInferenceResult visitVariableGet(
VariableGet node, DartType typeContext) {
if (node is! VariableGetImpl) {
// This node is created as part of a lowering and doesn't need inference.
return new ExpressionInferenceResult(
node.promotedType ?? node.variable.type, node);
}
VariableDeclarationImpl variable = node.variable as VariableDeclarationImpl;
DartType? promotedType;
DartType declaredOrInferredType = variable.lateType ?? variable.type;
if (isExtensionThis(variable)) {
flowAnalysis.thisOrSuper(node, variable.type, isSuper: true);
} else if (isNonNullableByDefault && node.forNullGuardedAccess) {
DartType nonNullableType = variable.type.toNonNull();
if (nonNullableType != variable.type) {
promotedType = nonNullableType;
}
} else if (!variable.isLocalFunction) {
// Don't promote local functions.
promotedType = flowAnalysis.variableRead(node, variable);
}
if (promotedType != null) {
instrumentation?.record(uriForInstrumentation, node.fileOffset,
'promotedType', new InstrumentationValueForType(promotedType));
}
node.promotedType = promotedType;
DartType resultType = promotedType ?? declaredOrInferredType;
Expression resultExpression;
if (variable.isLocalFunction) {
return instantiateTearOff(resultType, typeContext, node);
} else if (variable.lateGetter != null) {
resultExpression = new LocalFunctionInvocation(variable.lateGetter!,
new Arguments(<Expression>[])..fileOffset = node.fileOffset,
functionType: variable.lateGetter!.type as FunctionType)
..fileOffset = node.fileOffset;
// Future calls to flow analysis will be using `resultExpression` to refer
// to the variable get, so instruct flow analysis to forward the
// expression information.
flowAnalysis.forwardExpression(resultExpression, node);
} else {
resultExpression = node;
}
bool isUnassigned = !flowAnalysis.isAssigned(variable);
if (isUnassigned) {
dataForTesting?.flowAnalysisResult.potentiallyUnassignedNodes.add(node);
}
bool isDefinitelyUnassigned = flowAnalysis.isUnassigned(variable);
if (isDefinitelyUnassigned) {
dataForTesting?.flowAnalysisResult.definitelyUnassignedNodes.add(node);
}
if (isNonNullableByDefault) {
// Synthetic variables, local functions, and variables with
// invalid types aren't checked.
if (variable.name != null &&
!variable.isLocalFunction &&
declaredOrInferredType is! InvalidType) {
if (variable.isLate || variable.lateGetter != null) {
if (isDefinitelyUnassigned) {
String name = variable.lateName ?? variable.name!;
return new ExpressionInferenceResult(
resultType,
helper.wrapInProblem(
resultExpression,
templateLateDefinitelyUnassignedError.withArguments(name),
node.fileOffset,
name.length));
}
} else {
if (isUnassigned) {
if (variable.isFinal) {
return new ExpressionInferenceResult(
resultType,
helper.wrapInProblem(
resultExpression,
templateFinalNotAssignedError
.withArguments(node.variable.name!),
node.fileOffset,
node.variable.name!.length));
} else if (declaredOrInferredType.isPotentiallyNonNullable) {
return new ExpressionInferenceResult(
resultType,
helper.wrapInProblem(
resultExpression,
templateNonNullableNotAssignedError
.withArguments(node.variable.name!),
node.fileOffset,
node.variable.name!.length));
}
}
}
}
}
return new ExpressionInferenceResult(resultType, resultExpression);
}
@override
StatementInferenceResult visitWhileStatement(WhileStatement node) {
flowAnalysis.whileStatement_conditionBegin(node);
InterfaceType expectedType =
coreTypes.boolRawType(libraryBuilder.nonNullable);
ExpressionInferenceResult conditionResult =
inferExpression(node.condition, expectedType, isVoidAllowed: false);
Expression condition =
ensureAssignableResult(expectedType, conditionResult).expression;
node.condition = condition..parent = node;
flowAnalysis.whileStatement_bodyBegin(node, node.condition);
StatementInferenceResult bodyResult = inferStatement(node.body);
if (bodyResult.hasChanged) {
node.body = bodyResult.statement..parent = node;
}
flowAnalysis.whileStatement_end();
return const StatementInferenceResult();
}
@override
StatementInferenceResult visitYieldStatement(YieldStatement node) {
ExpressionInferenceResult expressionResult;
DartType typeContext = closureContext.yieldContext;
if (node.isYieldStar && typeContext is! UnknownType) {
typeContext = wrapType(
typeContext,
closureContext.isAsync
? coreTypes.streamClass
: coreTypes.iterableClass,
libraryBuilder.nonNullable);
}
expressionResult =
inferExpression(node.expression, typeContext, isVoidAllowed: true);
closureContext.handleYield(node, expressionResult);
return const StatementInferenceResult();
}
@override
ExpressionInferenceResult visitLoadLibrary(
covariant LoadLibraryImpl node, DartType typeContext) {
DartType inferredType = typeSchemaEnvironment.futureType(
const DynamicType(), libraryBuilder.nonNullable);
if (node.arguments != null) {
FunctionType calleeType =
new FunctionType([], inferredType, libraryBuilder.nonNullable);
inferInvocation(this, typeContext, node.fileOffset, calleeType,
node.arguments! as ArgumentsImpl);
}
return new ExpressionInferenceResult(inferredType, node);
}
ExpressionInferenceResult visitLoadLibraryTearOff(
LoadLibraryTearOff node, DartType typeContext) {
DartType inferredType = new FunctionType(
[],
typeSchemaEnvironment.futureType(
const DynamicType(), libraryBuilder.nonNullable),
libraryBuilder.nonNullable);
Expression replacement = new StaticTearOff(node.target)
..fileOffset = node.fileOffset;
return new ExpressionInferenceResult(inferredType, replacement);
}
@override
ExpressionInferenceResult visitCheckLibraryIsLoaded(
CheckLibraryIsLoaded node, DartType typeContext) {
// TODO(cstefantsova): Figure out the suitable nullability for that.
return new ExpressionInferenceResult(
coreTypes.objectRawType(libraryBuilder.nullable), node);
}
ExpressionInferenceResult visitEquals(
EqualsExpression node, DartType typeContext) {
ExpressionInferenceResult leftResult =
inferExpression(node.left, const UnknownType());
return _computeEqualsExpression(node.fileOffset, leftResult.expression,
leftResult.inferredType, node.right,
isNot: node.isNot);
}
ExpressionInferenceResult visitBinary(
BinaryExpression node, DartType typeContext) {
ExpressionInferenceResult leftResult =
inferExpression(node.left, const UnknownType());
Map<DartType, NonPromotionReason> Function() whyNotPromoted =
flowAnalysis.whyNotPromoted(leftResult.expression);
return _computeBinaryExpression(
node.fileOffset,
typeContext,
leftResult.expression,
leftResult.inferredType,
node.binaryName,
node.right,
whyNotPromoted);
}
ExpressionInferenceResult visitUnary(
UnaryExpression node, DartType typeContext) {
ExpressionInferenceResult? expressionResult;
if (node.unaryName == unaryMinusName) {
// Replace integer literals in a double context with the corresponding
// double literal if it's exact. For double literals, the negation is
// folded away. In any non-double context, or if there is no exact
// double value, then the corresponding integer literal is left. The
// negation is not folded away so that platforms with web literals can
// distinguish between (non-negated) 0x8000000000000000 represented as
// integer literal -9223372036854775808 which should be a positive number,
// and negated 9223372036854775808 represented as
// -9223372036854775808.unary-() which should be a negative number.
if (node.expression is IntJudgment) {
IntJudgment receiver = node.expression as IntJudgment;
if (isDoubleContext(typeContext)) {
double? doubleValue = receiver.asDouble(negated: true);
if (doubleValue != null) {
Expression replacement = new DoubleLiteral(doubleValue)
..fileOffset = node.fileOffset;
DartType inferredType =
coreTypes.doubleRawType(libraryBuilder.nonNullable);
return new ExpressionInferenceResult(inferredType, replacement);
}
}
Expression? error = checkWebIntLiteralsErrorIfUnexact(
receiver.value, receiver.literal, receiver.fileOffset);
if (error != null) {
return new ExpressionInferenceResult(const DynamicType(), error);
}
} else if (node.expression is ShadowLargeIntLiteral) {
ShadowLargeIntLiteral receiver =
node.expression as ShadowLargeIntLiteral;
if (!receiver.isParenthesized) {
if (isDoubleContext(typeContext)) {
double? doubleValue = receiver.asDouble(negated: true);
if (doubleValue != null) {
Expression replacement = new DoubleLiteral(doubleValue)
..fileOffset = node.fileOffset;
DartType inferredType =
coreTypes.doubleRawType(libraryBuilder.nonNullable);
return new ExpressionInferenceResult(inferredType, replacement);
}
}
int? intValue = receiver.asInt64(negated: true);
if (intValue == null) {
Expression error = helper.buildProblem(
templateIntegerLiteralIsOutOfRange
.withArguments(receiver.literal),
receiver.fileOffset,
receiver.literal.length);
return new ExpressionInferenceResult(const DynamicType(), error);
}
Expression? error = checkWebIntLiteralsErrorIfUnexact(
intValue, receiver.literal, receiver.fileOffset);
if (error != null) {
return new ExpressionInferenceResult(const DynamicType(), error);
}
expressionResult = new ExpressionInferenceResult(
coreTypes.intRawType(libraryBuilder.nonNullable),
new IntLiteral(-intValue)
..fileOffset = node.expression.fileOffset);
}
}
}
if (expressionResult == null) {
expressionResult = inferExpression(node.expression, const UnknownType());
}
Map<DartType, NonPromotionReason> Function() whyNotPromoted =
flowAnalysis.whyNotPromoted(expressionResult.expression);
return _computeUnaryExpression(node.fileOffset, expressionResult.expression,
expressionResult.inferredType, node.unaryName, whyNotPromoted);
}
ExpressionInferenceResult visitParenthesized(
ParenthesizedExpression node, DartType typeContext) {
return inferExpression(node.expression, typeContext, isVoidAllowed: true);
}
ExpressionInferenceResult visitInternalRecordLiteral(
InternalRecordLiteral node, DartType typeContext) {
List<Expression> positional = node.positional;
List<NamedExpression> namedUnsorted = node.named;
List<NamedExpression> named = namedUnsorted;
Map<String, NamedExpression>? namedElements = node.namedElements;
List<Object> originalElementOrder = node.originalElementOrder;
List<VariableDeclaration>? hoistedExpressions;
List<DartType>? positionalTypeContexts;
Map<String, DartType>? namedTypeContexts;
if (typeContext is RecordType &&
typeContext.positional.length == positional.length &&
typeContext.named.length == namedUnsorted.length) {
namedTypeContexts = <String, DartType>{};
for (NamedType namedType in typeContext.named) {
namedTypeContexts[namedType.name] = namedType.type;
}
bool sameNames = true;
for (int i = 0; sameNames && i < namedUnsorted.length; i++) {
if (!namedTypeContexts.containsKey(namedUnsorted[i].name)) {
sameNames = false;
}
}
if (sameNames) {
positionalTypeContexts = typeContext.positional;
} else {
namedTypeContexts = null;
}
}
List<DartType> positionalTypes;
// ignore: UNUSED_LOCAL_VARIABLE
List<NamedType> namedTypes;
if (namedElements == null) {
positionalTypes = [];
namedTypes = [];
for (int index = 0; index < positional.length; index++) {
Expression expression = positional[index];
DartType contextType =
positionalTypeContexts?[index] ?? const UnknownType();
ExpressionInferenceResult expressionResult =
inferExpression(expression, contextType);
if (contextType is! UnknownType) {
expressionResult = coerceExpressionForAssignment(
contextType, expressionResult,
treeNodeForTesting: node) ??
expressionResult;
}
positionalTypes.add(
expressionResult.postCoercionType ?? expressionResult.inferredType);
positional[index] = expressionResult.expression;
}
} else {
List<String> sortedNames = namedElements.keys.toList()..sort();
positionalTypes =
new List<DartType>.filled(positional.length, const UnknownType());
Map<String, DartType> namedElementTypes = {};
// Index into [sortedNames] of the named element we expected to find
// next, for the named elements to be sorted. This also used to detect
// when all named elements have been seen, even when they are not sorted.
int nameIndex = sortedNames.length - 1;
// Index into [positional] of the positional element we find next.
int positionalIndex = positional.length - 1;
// For const literals we don't hoist to avoid using let variables in
// inside constants. Since the elements of the literal must be constant
// themselves, we know that there is no side effects of performing
// constant evaluation out of order.
final bool enableHoisting = !node.isConst;
// Set to `true` if we need to hoist all preceding elements.
bool needsHoisting = false;
// Set to `true` if named elements need to be sorted. This implies that
// we will need to hoist preceding elements.
bool namedNeedsSorting = false;
// We run through the elements in reverse order to determine which
// expressions we need to hoist. When we observe an element out of order,
// either positional after named or unsorted named, all preceding
// elements must be hoisted to retain the original evaluation order.
for (int index = originalElementOrder.length - 1; index >= 0; index--) {
Object element = originalElementOrder[index];
if (element is NamedExpression) {
DartType contextType =
namedTypeContexts?[element.name] ?? const UnknownType();
ExpressionInferenceResult expressionResult =
inferExpression(element.value, contextType);
if (contextType is! UnknownType) {
expressionResult = coerceExpressionForAssignment(
contextType, expressionResult,
treeNodeForTesting: node) ??
expressionResult;
}
Expression expression = expressionResult.expression;
DartType type = expressionResult.postCoercionType ??
expressionResult.inferredType;
// TODO(johnniwinther): Should we use [isPureExpression] as is, make
// it include (simple) literals, or add a new predicate?
if (needsHoisting && !isPureExpression(expression)) {
// We hoist the value of the [NamedExpression] into a synthesized
// variable, and replace the value with a read of the variable.
VariableDeclaration variable = createVariable(expression, type);
hoistedExpressions ??= [];
hoistedExpressions.add(variable);
element.value = createVariableGet(variable)..parent = element;
} else {
element.value = expression..parent = element;
}
namedElementTypes[element.name] = type;
if (!namedNeedsSorting && element.name != sortedNames[nameIndex]) {
// Named elements are not sorted, so we need to hoist and sort them.
namedNeedsSorting = true;
needsHoisting = enableHoisting;
}
nameIndex--;
} else {
DartType contextType =
positionalTypeContexts?[positionalIndex] ?? const UnknownType();
ExpressionInferenceResult expressionResult =
inferExpression(element as Expression, contextType);
if (contextType is! UnknownType) {
expressionResult = coerceExpressionForAssignment(
contextType, expressionResult,
treeNodeForTesting: node) ??
expressionResult;
}
Expression expression = expressionResult.expression;
DartType type = expressionResult.postCoercionType ??
expressionResult.inferredType;
// TODO(johnniwinther): Should we use [isPureExpression] as is, make
// it include (simple) literals, or add a new predicate?
if (needsHoisting && !isPureExpression(expression)) {
// We hoist the positional element into a synthesized variable, and
// replace the element in [positional] with a read of the variable.
VariableDeclaration variable = createVariable(expression, type);
hoistedExpressions ??= [];
hoistedExpressions.add(variable);
positional[positionalIndex] = createVariableGet(variable);
} else {
positional[positionalIndex] = expression;
if (nameIndex >= 0) {
// We have not seen all named elements yet, so we must hoist the
// remaining named elements and the preceding positional elements.
needsHoisting = enableHoisting;
}
}
positionalTypes[positionalIndex] = type;
positionalIndex--;
}
}
namedTypes =
new List<NamedType>.generate(sortedNames.length, (int index) {
String name = sortedNames[index];
return new NamedType(name, namedElementTypes[name]!);
});
if (namedNeedsSorting) {
// The [named] elements need to be sorted.
named = [];
for (String name in sortedNames) {
named.add(namedElements[name]!);
}
}
}
DartType type;
Expression result;
if (!libraryBuilder.libraryFeatures.records.isEnabled) {
// TODO(johnniwinther): Remove this when backends can handle record
// literals and types without crashing.
type = const InvalidType();
result = new InvalidExpression(templateExperimentNotEnabledOffByDefault
.withArguments(ExperimentalFlag.records.name)
.withoutLocation()
.problemMessage);
} else {
result = new RecordLiteral(
positional,
named,
type = new RecordType(
positionalTypes, namedTypes, libraryBuilder.nonNullable),
isConst: node.isConst)
..fileOffset = node.fileOffset;
}
if (hoistedExpressions != null) {
for (VariableDeclaration variable in hoistedExpressions) {
result = createLet(variable, result);
}
}
return new ExpressionInferenceResult(type, result);
}
/// Pops the top entry off of [_rewriteStack].
Object? popRewrite([NullValue? nullValue]) {
Object entry = _rewriteStack.removeLast();
if (_debugRewriteStack) {
assert(_debugPrint('POP ${entry.runtimeType} $entry'));
}
if (entry is! NullValue) {
return entry;
}
assert(nullValue == entry,
"Unexpected null value. Expected ${nullValue}, actual $entry");
return null;
}
/// Pushes an entry onto [_rewriteStack].
void pushRewrite(Object node) {
if (_debugRewriteStack) {
assert(_debugPrint('PUSH ${node.runtimeType} $node'));
}
_rewriteStack.add(node);
}
/// Helper function used to print information to the console in debug mode.
/// This method returns `true` so that it can be conveniently called inside of
/// an `assert` statement.
bool _debugPrint(String s) {
print(s);
return true;
}
@override
ExpressionTypeAnalysisResult<DartType> dispatchExpression(
Expression node, DartType context) {
// Normally the CFE performs expression coercion in the process of type
// inference of the nodes where an assignment is executed. The inference on
// the pattern-related nodes is driven by the shared analysis, and some of
// such nodes perform assignments. Here we determine if we're inferring the
// expressions of one of such nodes, and perform the coercion if needed.
TreeNode? parent = node.parent;
// The case of pattern variable declaration. The initializer expression is
// assigned to the pattern, and so the coercion needs to be performed.
bool needsCoercion =
parent is PatternVariableDeclaration && parent.initializer == node;
// The case of pattern assignment. The expression is assigned to the
// pattern, and so the coercion needs to be performed.
needsCoercion = needsCoercion ||
parent is PatternAssignment && parent.expression == node;
// The constant expressions in relational patterns are considered to be
// passed into the corresponding operator, and so the coercion needs to be
// performed.
needsCoercion = needsCoercion ||
parent is RelationalPattern && parent.expression == node;
ExpressionInferenceResult expressionResult =
// TODO(johnniwinther): Handle [isVoidAllowed] through
// [dispatchExpression].
inferExpression(node, context, isVoidAllowed: true).stopShorting();
if (needsCoercion) {
expressionResult = coerceExpressionForAssignment(
context, expressionResult,
treeNodeForTesting: node) ??
expressionResult;
}
pushRewrite(expressionResult.expression);
// The shared analysis logic uses the convention that the expressions passed
// to flow analysis are the original (pre-lowered) expressions, whereas the
// expressions passed to flow analysis by the CFE are the lowered
// expressions. Since the caller of `dispatchExpression` is the shared
// analysis logic, we need to use `flow.forwardExpression` let flow analysis
// know that in future, we'll be referring to the expression using `node`
// (its pre-lowered form) rather than `expressionResult.expression` (its
// post-lowered form).
//
// TODO(paulberry): eliminate the need for this--see
// https://github.com/dart-lang/sdk/issues/52189.
flow.forwardExpression(node, expressionResult.expression);
return new SimpleTypeAnalysisResult(type: expressionResult.inferredType);
}
@override
PatternResult<DartType> dispatchPattern(
SharedMatchContext context, TreeNode node) {
if (node is Pattern) {
return node.accept1(this, context);
} else {
return analyzeConstantPattern(context, node, node as Expression);
}
}
@override
DartType dispatchPatternSchema(Node node) {
if (node is AndPattern) {
return analyzeLogicalAndPatternSchema(node.left, node.right);
} else if (node is AssignedVariablePattern) {
return analyzeAssignedVariablePatternSchema(node.variable);
} else if (node is CastPattern) {
return analyzeCastPatternSchema();
} else if (node is ConstantPattern) {
return analyzeConstantPatternSchema();
} else if (node is ListPattern) {
return analyzeListPatternSchema(
elementType: node.typeArgument, elements: node.patterns);
} else if (node is MapPattern) {
return analyzeMapPatternSchema(
typeArguments: node.keyType != null && node.valueType != null
? (keyType: node.keyType!, valueType: node.valueType!)
: null,
elements: node.entries);
} else if (node is NamedPattern) {
return dispatchPatternSchema(node.pattern);
} else if (node is NullAssertPattern) {
return analyzeNullCheckOrAssertPatternSchema(node.pattern,
isAssert: true);
} else if (node is NullCheckPattern) {
return analyzeNullCheckOrAssertPatternSchema(node.pattern,
isAssert: false);
} else if (node is ObjectPattern) {
return analyzeObjectPatternSchema(node.requiredType);
} else if (node is OrPattern) {
return analyzeLogicalOrPatternSchema(node.left, node.right);
} else if (node is RecordPattern) {
return analyzeRecordPatternSchema(
fields: <RecordPatternField<TreeNode, Pattern>>[
for (Pattern element in node.patterns)
if (element is NamedPattern)
new RecordPatternField<TreeNode, Pattern>(
node: element, name: element.name, pattern: element.pattern)
else
new RecordPatternField<TreeNode, Pattern>(
node: element, name: null, pattern: element)
]);
} else if (node is RelationalPattern) {
return analyzeRelationalPatternSchema();
} else if (node is RestPattern) {
// This pattern can't appear on it's own.
return const InvalidType();
} else if (node is VariablePattern) {
return analyzeDeclaredVariablePatternSchema(node.type);
} else if (node is WildcardPattern) {
return analyzeDeclaredVariablePatternSchema(node.type);
} else if (node is InvalidPattern) {
return const InvalidType();
} else {
return problems.unhandled("${node.runtimeType}", "dispatchPatternSchema",
node is TreeNode ? node.fileOffset : TreeNode.noOffset, helper.uri);
}
}
@override
void dispatchStatement(Statement statement) {
StatementInferenceResult result = inferStatement(statement);
pushRewrite(result.hasChanged ? result.statement : statement);
}
@override
void finishExpressionCase(Expression node, int caseIndex) {
SwitchExpressionCase switchExpressionCase =
(node as SwitchExpression).cases[caseIndex];
Object? rewrite = popRewrite();
if (!identical(switchExpressionCase.expression, rewrite)) {
switchExpressionCase.expression = rewrite as Expression
..parent = switchExpressionCase;
}
}
@override
void handleMergedStatementCase(covariant SwitchStatement node,
{required int caseIndex, required bool isTerminating}) {
SwitchCase case_ = node.cases[caseIndex];
int? stackBase;
assert(checkStackBase(
node, stackBase = stackHeight - (1 + case_.caseHeadCount)));
assert(checkStack(node, stackBase, [
/* body = */ ValueKinds.Statement,
/* case heads = */ ...repeatedKind(
ValueKinds.SwitchCase, case_.caseHeadCount),
]));
Statement body = case_.body;
Object? rewrite = popRewrite();
if (!identical(body, rewrite)) {
body = rewrite as Statement;
case_.body = body..parent = case_;
}
assert(checkStack(node, stackBase, [
/* case heads = */ ...repeatedKind(
ValueKinds.SwitchCase, case_.caseHeadCount)
]));
// When patterns are enable, if this is not the last case and it is not
// terminating, we insert a synthetic break.
if (libraryBuilder.libraryFeatures.patterns.isEnabled &&
!isTerminating &&
caseIndex < node.cases.length - 1) {
LabeledStatement switchLabel = node.parent as LabeledStatement;
BreakStatement syntheticBreak = new BreakStatement(switchLabel)
..fileOffset = TreeNode.noOffset;
if (body is Block) {
body.statements.add(syntheticBreak);
syntheticBreak.parent = body;
} else {
body = new Block([body, syntheticBreak])..fileOffset = body.fileOffset;
case_.body = body..parent = case_;
}
}
if (node is PatternSwitchStatement) {
if (case_ is PatternSwitchCase) {
assert(checkStack(node, stackBase, [
/* case heads = */ ...repeatedKind(
ValueKinds.SwitchCase, case_.patternGuards.length)
]));
for (int i = 0; i < case_.patternGuards.length; i++) {
popRewrite(); // CaseHead
}
} else {
popRewrite(); // CaseHead
}
} else {
if (case_ is SwitchCaseImpl) {
assert(checkStack(node, stackBase, [
/* case heads = */ ...repeatedKind(
ValueKinds.SwitchCase, case_.expressions.length)
]));
for (int i = 0; i < case_.expressions.length; i++) {
popRewrite(); // CaseHead
}
} else {
popRewrite(); // CaseHead
}
}
assert(checkStack(node, stackBase, [/*empty*/]));
pushRewrite(case_);
assert(checkStack(node, stackBase, [/* case = */ ValueKinds.SwitchCase]));
}
@override
FlowAnalysis<TreeNode, Statement, Expression, VariableDeclaration, DartType>
get flow => flowAnalysis;
@override
SwitchExpressionMemberInfo<TreeNode, Expression, VariableDeclaration>
getSwitchExpressionMemberInfo(Expression node, int index) {
SwitchExpressionCase switchExpressionCase =
(node as SwitchExpression).cases[index];
Pattern pattern = switchExpressionCase.patternGuard.pattern;
Map<String, VariableDeclaration> variables = {
for (VariableDeclaration declaredVariable in pattern.declaredVariables)
declaredVariable.name!: declaredVariable
};
return new SwitchExpressionMemberInfo<TreeNode, Expression,
VariableDeclaration>(
head: new CaseHeadOrDefaultInfo<TreeNode, Expression,
VariableDeclaration>(
pattern: pattern,
guard: switchExpressionCase.patternGuard.guard,
variables: variables),
expression: switchExpressionCase.expression);
}
@override
SwitchStatementMemberInfo<TreeNode, Statement, Expression,
VariableDeclaration>
getSwitchStatementMemberInfo(
covariant SwitchStatement node, int caseIndex) {
SwitchCase case_ = node.cases[caseIndex];
if (case_ is SwitchCaseImpl) {
return new SwitchStatementMemberInfo(heads: [
for (Expression expression in case_.expressions)
new CaseHeadOrDefaultInfo(
pattern: expression,
variables: {},
),
if (case_.isDefault)
new CaseHeadOrDefaultInfo(
pattern: null,
variables: {},
)
], body: [
case_.body
], variables: {}, hasLabels: case_.hasLabel);
} else {
case_ as PatternSwitchCase;
return new SwitchStatementMemberInfo(heads: [
for (PatternGuard patternGuard in case_.patternGuards)
new CaseHeadOrDefaultInfo(
pattern: patternGuard.pattern,
guard: patternGuard.guard,
variables: {
for (VariableDeclaration variable
in patternGuard.pattern.declaredVariables)
variable.name!: variable
},
),
if (case_.isDefault)
new CaseHeadOrDefaultInfo(
pattern: null,
variables: {},
)
], body: [
case_.body
], variables: {
for (VariableDeclaration jointVariable in case_.jointVariables)
jointVariable.name!: jointVariable
}, hasLabels: case_.hasLabel);
}
}
@override
void handleCaseHead(
covariant /* SwitchStatement | SwitchExpression */ Object node,
{required int caseIndex,
required int subIndex}) {
int? stackBase;
assert(checkStackBase(node as TreeNode, stackBase = stackHeight - 2));
void handleConstantPattern(Expression expression) {
Set<Field?>? enumFields = _enumFields;
if (enumFields != null) {
if (expression is StaticGet) {
enumFields.remove(expression.target);
} else if (expression is NullLiteral) {
enumFields.remove(null);
}
}
}
if (node is SwitchStatement) {
assert(checkStack(node, stackBase, [
/* guard = */ ValueKinds.ExpressionOrNull,
/* pattern or expression = */ unionOfKinds(
[ValueKinds.Pattern, ValueKinds.Expression]),
]));
Object? guardRewrite = popRewrite(NullValues.Expression);
assert(checkStack(node, stackBase, [
/* pattern or expression = */ unionOfKinds(
[ValueKinds.Pattern, ValueKinds.Expression]),
]));
SwitchCase case_ = node.cases[caseIndex];
if (case_ is SwitchCaseImpl) {
Expression expression = case_.expressions[subIndex];
Object? rewrite = popRewrite();
assert(checkStack(node, stackBase, [/*empty*/]));
if (!identical(expression, rewrite)) {
expression = rewrite as Expression;
case_.expressions[subIndex] = expression..parent = case_;
}
handleConstantPattern(expression);
pushRewrite(case_);
} else {
PatternGuard patternGuard =
(case_ as PatternSwitchCase).patternGuards[subIndex];
if (guardRewrite != null &&
!identical(guardRewrite, patternGuard.guard)) {
patternGuard.guard = (guardRewrite as Expression)
..parent = patternGuard;
}
Object? rewrite = popRewrite();
if (!identical(rewrite, patternGuard.pattern)) {
patternGuard.pattern = (rewrite as Pattern)..parent = patternGuard;
}
if (patternGuard.guard == null) {
Pattern pattern = patternGuard.pattern;
if (pattern is ConstantPattern) {
handleConstantPattern(pattern.expression);
}
}
pushRewrite(case_);
}
} else {
SwitchExpressionCase switchExpressionCase =
(node as SwitchExpression).cases[caseIndex];
PatternGuard patternGuard = switchExpressionCase.patternGuard;
assert(checkStack(node, stackBase, [
/* guard = */ ValueKinds.ExpressionOrNull,
/* pattern = */ ValueKinds.Pattern,
]));
Object? guard = popRewrite(NullValues.Expression);
if (guard != null && !identical(patternGuard.guard, guard)) {
patternGuard.guard = (guard as Expression)..parent = patternGuard;
}
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
Object? pattern = popRewrite();
assert(checkStack(node, stackBase, [/*empty*/]));
if (pattern != null && !identical(patternGuard.pattern, pattern)) {
patternGuard.pattern = (pattern as Pattern)..parent = patternGuard;
}
if (patternGuard.guard == null) {
Pattern pattern = patternGuard.pattern;
if (pattern is ConstantPattern) {
handleConstantPattern(pattern.expression);
}
}
}
}
@override
void handleCase_afterCaseHeads(
Statement node, int caseIndex, Iterable<VariableDeclaration> variables) {}
@override
void handleDefault(
TreeNode node, {
required int caseIndex,
required int subIndex,
}) {}
@override
void handleNoStatement(Statement node) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
pushRewrite(NullValues.Statement);
assert(checkStack(node, stackBase, [
/* statement = */ ValueKinds.StatementOrNull,
]));
}
@override
void handleNoGuard(TreeNode node, int caseIndex) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
pushRewrite(NullValues.Expression);
assert(checkStack(node, stackBase, [
/* expression = */ ValueKinds.ExpressionOrNull,
]));
}
@override
void handleSwitchBeforeAlternative(
TreeNode node, {
required int caseIndex,
required int subIndex,
}) {}
@override
void handleSwitchScrutinee(DartType type) {
if ((!options.patternsEnabled) &&
type is InterfaceType &&
type.classNode.isEnum) {
_enumFields = <Field?>{
...type.classNode.fields.where((Field field) => field.isEnumElement),
if (type.isPotentiallyNullable) null
};
} else {
_enumFields = null;
}
pushRewrite(type);
}
@override
bool isLegacySwitchExhaustive(TreeNode node, DartType expressionType) {
Set<Field?>? enumFields = _enumFields;
return enumFields != null && enumFields.isEmpty;
}
@override
bool isVariablePattern(TreeNode node) {
throw new UnimplementedError('TODO(paulberry)');
}
@override
void setVariableType(VariableDeclaration variable, DartType type) {
variable.type = type;
}
@override
DartType variableTypeFromInitializerType(DartType type) {
// TODO(paulberry): make a test verifying that we don't need to pass
// `forSyntheticVariable: true` (and possibly a language issue)
return inferDeclarationType(type);
}
@override
void checkCleanState() {
assert(_rewriteStack.isEmpty);
}
@override
PatternResult<DartType> visitVariablePattern(
VariablePattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
DeclaredVariablePatternResult<DartType, InvalidExpression> analysisResult =
analyzeDeclaredVariablePattern(
context, node, node.variable, node.variable.name!, node.type);
node.matchedValueType = analysisResult.matchedValueType;
Pattern? replacement;
InvalidExpression? error =
analysisResult.patternTypeMismatchInIrrefutableContextError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
DartType inferredType = analysisResult.staticType;
instrumentation?.record(uriForInstrumentation, node.variable.fileOffset,
'type', new InstrumentationValueForType(inferredType));
if (node.type == null) {
node.variable.type = inferredType;
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitWildcardPattern(
WildcardPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
WildcardPatternResult<DartType, InvalidExpression> analysisResult =
analyzeWildcardPattern(
context: context, node: node, declaredType: node.type);
Pattern? replacement;
InvalidExpression? error =
analysisResult.patternTypeMismatchInIrrefutableContextError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitConstantPattern(
ConstantPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
ConstantPatternResult<DartType, InvalidExpression> analysisResult =
analyzeConstantPattern(context, node, node.expression);
Pattern? replacement;
InvalidExpression? error =
analysisResult.refutablePatternInIrrefutableContextError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
DartType expressionType =
node.expressionType = analysisResult.expressionType;
ObjectAccessTarget equalsInvokeTarget = findInterfaceMember(
expressionType, equalsName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(equalsInvokeTarget.isInstanceMember ||
equalsInvokeTarget.isObjectMember ||
equalsInvokeTarget.isNever);
node.equalsTarget = equalsInvokeTarget.classMember as Procedure;
node.equalsType = equalsInvokeTarget.getFunctionType(this);
assert(checkStack(node, stackBase, [
/* expression = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite();
if (!identical(node.expression, rewrite)) {
node.expression = (rewrite as Expression)..parent = node;
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitAndPattern(
AndPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
PatternResult<DartType> analysisResult =
analyzeLogicalAndPattern(context, node, node.left, node.right);
assert(checkStack(node, stackBase, [
/* right = */ ValueKinds.Pattern,
/* left = */ ValueKinds.Pattern,
]));
Object? rewrite = popRewrite();
if (!identical(rewrite, node.right)) {
node.right = (rewrite as Pattern)..parent = node;
}
rewrite = popRewrite();
if (!identical(rewrite, node.left)) {
node.left = (rewrite as Pattern)..parent = node;
}
pushRewrite(node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitOrPattern(
OrPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
LogicalOrPatternResult<DartType, InvalidExpression> analysisResult =
analyzeLogicalOrPattern(context, node, node.left, node.right);
assert(checkStack(node, stackBase, [
/* right = */ ValueKinds.Pattern,
/* left = */ ValueKinds.Pattern,
]));
Pattern? replacement;
InvalidExpression? error =
analysisResult.refutablePatternInIrrefutableContextError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
Object? rewrite = popRewrite();
if (!identical(rewrite, node.right)) {
node.right = (rewrite as Pattern)..parent = node;
}
rewrite = popRewrite();
if (!identical(rewrite, node.left)) {
node.left = (rewrite as Pattern)..parent = node;
}
Map<String, VariableDeclaration> leftDeclaredVariablesByName = {
for (VariableDeclaration variable in node.left.declaredVariables)
variable.name!: variable
};
Map<String, VariableDeclaration> jointVariableNames = {
for (VariableDeclaration variable in node.orPatternJointVariables)
variable.name!: variable
};
for (VariableDeclaration rightVariable in node.right.declaredVariables) {
String rightVariableName = rightVariable.name!;
VariableDeclaration? leftVariable =
leftDeclaredVariablesByName[rightVariableName];
VariableDeclaration? jointVariable =
jointVariableNames[rightVariableName];
if (leftVariable != null && jointVariable != null) {
if (leftVariable.type != rightVariable.type ||
leftVariable.isFinal != rightVariable.isFinal) {
helper.addProblem(
templateJointPatternVariablesMismatch
.withArguments(rightVariableName),
leftVariable.fileOffset,
rightVariableName.length);
} else {
jointVariable.isFinal = rightVariable.isFinal;
jointVariable.type = rightVariable.type;
}
}
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitCastPattern(
CastPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
PatternResult<DartType> analysisResult = analyzeCastPattern(
context: context,
pattern: node,
innerPattern: node.pattern,
requiredType: node.type,
);
assert(checkStack(node, stackBase, [
/* subpattern = */ ValueKinds.Pattern,
]));
Object? rewrite = popRewrite();
if (!identical(rewrite, node.pattern)) {
node.pattern = (rewrite as Pattern)..parent = node;
}
pushRewrite(node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitNullAssertPattern(
NullAssertPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
NullCheckOrAssertPatternResult<DartType, InvalidExpression> analysisResult =
analyzeNullCheckOrAssertPattern(context, node, node.pattern,
isAssert: true);
assert(checkStack(node, stackBase, [
/* subpattern = */ ValueKinds.Pattern,
]));
Pattern? replacement;
InvalidExpression? error =
analysisResult.refutablePatternInIrrefutableContextError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
Object? rewrite = popRewrite();
if (!identical(rewrite, node.pattern)) {
node.pattern = (rewrite as Pattern)..parent = node;
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitNullCheckPattern(
NullCheckPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
NullCheckOrAssertPatternResult<DartType, InvalidExpression> analysisResult =
analyzeNullCheckOrAssertPattern(context, node, node.pattern,
isAssert: false);
assert(checkStack(node, stackBase, [
/* subpattern = */ ValueKinds.Pattern,
]));
Object? rewrite = popRewrite();
if (!identical(rewrite, node.pattern)) {
node.pattern = (rewrite as Pattern)..parent = node;
}
pushRewrite(node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitListPattern(
ListPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
ListPatternResult<DartType, InvalidExpression> analysisResult =
analyzeListPattern(context, node,
elements: node.patterns, elementType: node.typeArgument);
DartType matchedValueType =
node.matchedValueType = analysisResult.matchedValueType;
assert(checkStack(node, stackBase, [
/* subpatterns = */ ...repeatedKind(
ValueKinds.Pattern, node.patterns.length)
]));
Pattern? replacement;
InvalidExpression? error =
analysisResult.patternTypeMismatchInIrrefutableContextError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
for (int i = node.patterns.length - 1; i >= 0; i--) {
Object? rewrite = popRewrite();
InvalidExpression? error = analysisResult.duplicateRestPatternErrors?[i];
if (error != null) {
node.patterns[i] = new InvalidPattern(error,
declaredVariables: node.patterns[i].declaredVariables)
..fileOffset = error.fileOffset
..parent = node;
} else if (!identical(rewrite, node.patterns[i])) {
node.patterns[i] = (rewrite as Pattern)..parent = node;
}
}
// TODO(johnniwinther): The required type computed by the type analyzer
// isn't trivially `List<dynamic>` in all cases. Does that matter for the
// lowering?
DartType requiredType = node.requiredType = analysisResult.requiredType;
node.needsCheck =
_needsCheck(matchedType: matchedValueType, requiredType: requiredType);
DartType lookupType;
if (node.needsCheck) {
lookupType = node.lookupType = requiredType;
} else {
lookupType = node.lookupType = matchedValueType;
}
ObjectAccessTarget lengthTarget = findInterfaceMember(
lookupType, lengthName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(lengthTarget.isInstanceMember);
DartType lengthType = node.lengthType = lengthTarget.getGetterType(this);
node.lengthTarget = lengthTarget.classMember!;
ObjectAccessTarget sublistInvokeTarget = findInterfaceMember(
lookupType, sublistName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(sublistInvokeTarget.isInstanceMember);
node.sublistTarget = sublistInvokeTarget.classMember as Procedure;
node.sublistType = sublistInvokeTarget.getFunctionType(this);
ObjectAccessTarget minusTarget = findInterfaceMember(
lengthType, minusName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(minusTarget.isInstanceMember);
assert(minusTarget.isSpecialCasedBinaryOperator(this));
node.minusTarget = minusTarget.classMember as Procedure;
node.minusType = replaceReturnType(
minusTarget.getFunctionType(this),
typeSchemaEnvironment.getTypeOfSpecialCasedBinaryOperator(
lengthType, coreTypes.intNonNullableRawType,
isNonNullableByDefault: isNonNullableByDefault));
ObjectAccessTarget indexGetTarget = findInterfaceMember(
lookupType, indexGetName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(indexGetTarget.isInstanceMember);
node.indexGetTarget = indexGetTarget.classMember as Procedure;
node.indexGetType = indexGetTarget.getFunctionType(this);
for (Pattern pattern in node.patterns) {
if (pattern is RestPattern) {
node.hasRestPattern = true;
break;
}
}
if (node.hasRestPattern) {
ObjectAccessTarget greaterThanOrEqualTarget = findInterfaceMember(
lengthType, greaterThanOrEqualsName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(greaterThanOrEqualTarget.isInstanceMember);
node.lengthCheckTarget =
greaterThanOrEqualTarget.classMember as Procedure;
node.lengthCheckType = greaterThanOrEqualTarget.getFunctionType(this);
} else if (node.patterns.isEmpty) {
ObjectAccessTarget lessThanOrEqualsInvokeTarget = findInterfaceMember(
lengthType, lessThanOrEqualsName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(lessThanOrEqualsInvokeTarget.isInstanceMember ||
lessThanOrEqualsInvokeTarget.isObjectMember);
node.lengthCheckTarget =
lessThanOrEqualsInvokeTarget.classMember as Procedure;
node.lengthCheckType = lessThanOrEqualsInvokeTarget.getFunctionType(this);
} else {
ObjectAccessTarget equalsInvokeTarget = findInterfaceMember(
lengthType, equalsName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(equalsInvokeTarget.isInstanceMember ||
equalsInvokeTarget.isObjectMember);
node.lengthCheckTarget = equalsInvokeTarget.classMember as Procedure;
node.lengthCheckType = equalsInvokeTarget.getFunctionType(this);
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
bool _needsCast(
{required DartType matchedType, required DartType requiredType}) {
return !typeSchemaEnvironment.isSubtypeOf(
matchedType, requiredType, SubtypeCheckMode.withNullabilities);
}
bool _needsCheck(
{required DartType matchedType, required DartType requiredType}) {
// TODO(johnniwinther): Should we use `isSubtypeOf` here instead?
return !isAssignable(requiredType, matchedType) ||
matchedType is InvalidType ||
matchedType is DynamicType;
}
@override
PatternResult<DartType> visitObjectPattern(
ObjectPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
ObjectPatternResult<DartType, InvalidExpression> analysisResult =
analyzeObjectPattern(context, node,
fields: <RecordPatternField<TreeNode, Pattern>>[
for (NamedPattern field in node.fields)
new RecordPatternField(
node: field, name: field.name, pattern: field.pattern)
]);
DartType matchedValueType =
node.matchedValueType = analysisResult.matchedValueType;
assert(checkStack(node, stackBase, [
/* subpatterns = */ ...repeatedKind(
ValueKinds.Pattern, node.fields.length)
]));
node.requiredType = analysisResult.requiredType;
Pattern? replacement;
InvalidExpression? error =
analysisResult.patternTypeMismatchInIrrefutableContextError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
for (int i = node.fields.length - 1; i >= 0; i--) {
NamedPattern field = node.fields[i];
Object? rewrite = popRewrite();
InvalidExpression? error =
analysisResult.duplicateRecordPatternFieldErrors?[i];
if (error != null) {
field.pattern = new InvalidPattern(error,
declaredVariables: field.pattern.declaredVariables)
..fileOffset = error.fileOffset
..parent = field;
} else if (!identical(rewrite, field.pattern)) {
field.pattern = (rewrite as Pattern)..parent = field;
}
}
node.needsCheck = _needsCheck(
matchedType: matchedValueType, requiredType: node.requiredType);
if (node.needsCheck) {
node.lookupType = node.requiredType;
} else {
node.lookupType = matchedValueType;
}
for (NamedPattern field in node.fields) {
field.fieldName = new Name(field.name, libraryBuilder.library);
ObjectAccessTarget fieldTarget = findInterfaceMember(
node.requiredType, field.fieldName, field.fileOffset,
includeExtensionMethods: true, isSetter: false);
switch (fieldTarget.kind) {
case ObjectAccessTargetKind.instanceMember:
field.target = fieldTarget.classMember!;
field.resultType = fieldTarget.getGetterType(this);
field.accessKind = ObjectAccessKind.Instance;
break;
case ObjectAccessTargetKind.objectMember:
field.target = fieldTarget.classMember!;
field.resultType = fieldTarget.getGetterType(this);
field.accessKind = ObjectAccessKind.Object;
break;
case ObjectAccessTargetKind.recordNamed:
field.recordType =
node.requiredType.nonTypeVariableBound as RecordType;
field.accessKind = ObjectAccessKind.RecordNamed;
break;
case ObjectAccessTargetKind.recordIndexed:
field.recordType =
node.requiredType.nonTypeVariableBound as RecordType;
field.accessKind = ObjectAccessKind.RecordIndexed;
field.recordFieldIndex = fieldTarget.recordFieldIndex!;
break;
case ObjectAccessTargetKind.nullableInstanceMember:
case ObjectAccessTargetKind.nullableExtensionMember:
case ObjectAccessTargetKind.nullableExtensionTypeMember:
case ObjectAccessTargetKind.nullableRecordIndexed:
case ObjectAccessTargetKind.nullableRecordNamed:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
case ObjectAccessTargetKind.nullableExtensionTypeRepresentation:
field.pattern = new InvalidPattern(
createMissingPropertyGet(
field.fileOffset, node.requiredType, field.fieldName),
declaredVariables: field.pattern.declaredVariables)
..fileOffset = field.fileOffset
..parent = field;
field.accessKind = ObjectAccessKind.Error;
break;
case ObjectAccessTargetKind.invalid:
field.accessKind = ObjectAccessKind.Invalid;
break;
case ObjectAccessTargetKind.callFunction:
field.accessKind = ObjectAccessKind.FunctionTearOff;
break;
case ObjectAccessTargetKind.extensionTypeRepresentation:
field.accessKind = ObjectAccessKind.Direct;
field.resultType = fieldTarget.getGetterType(this);
case ObjectAccessTargetKind.superMember:
problems.unsupported(
'Object field target $fieldTarget', node.fileOffset, helper.uri);
case ObjectAccessTargetKind.extensionMember:
field.accessKind = ObjectAccessKind.Extension;
field.resultType = fieldTarget.getGetterType(this);
field.typeArguments = fieldTarget.receiverTypeArguments;
field.target = fieldTarget.tearoffTarget;
break;
case ObjectAccessTargetKind.extensionTypeMember:
field.accessKind = ObjectAccessKind.ExtensionType;
field.resultType = fieldTarget.getGetterType(this);
field.typeArguments = fieldTarget.receiverTypeArguments;
// TODO(johnniwinther): Extension type getters currently have no
// explicitly set tear-off target. Maybe they should.
field.target = fieldTarget.tearoffTarget ?? fieldTarget.member;
break;
case ObjectAccessTargetKind.dynamic:
field.accessKind = ObjectAccessKind.Dynamic;
break;
case ObjectAccessTargetKind.never:
field.accessKind = ObjectAccessKind.Dynamic;
break;
}
if (fieldTarget.isInstanceMember || fieldTarget.isObjectMember) {
// TODO(johnniwinther): Use [fieldTarget] to compute the checked type.
Member interfaceMember = fieldTarget.classMember!;
if (interfaceMember is Procedure) {
DartType typeToCheck = isNonNullableByDefault
? interfaceMember.function
.computeFunctionType(libraryBuilder.nonNullable)
: interfaceMember.function.returnType;
field.checkReturn =
InferenceVisitorBase.returnedTypeParametersOccurNonCovariantly(
interfaceMember.enclosingTypeDeclaration!, typeToCheck);
} else if (interfaceMember is Field) {
field.checkReturn =
InferenceVisitorBase.returnedTypeParametersOccurNonCovariantly(
interfaceMember.enclosingTypeDeclaration!,
interfaceMember.type);
}
}
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitRestPattern(
RestPattern node, SharedMatchContext context) {
// A rest pattern isn't a real pattern; this code should never be reached.
throw new StateError('visitRestPattern should never be reached');
}
@override
PatternResult<DartType> visitInvalidPattern(
InvalidPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
pushRewrite(node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return new PatternResult(matchedValueType: const InvalidType());
}
@override
PatternResult<DartType> visitRelationalPattern(
RelationalPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
RelationalPatternResult<DartType, InvalidExpression> analysisResult =
analyzeRelationalPattern(context, node, node.expression);
DartType matchedValueType =
node.matchedValueType = analysisResult.matchedValueType;
assert(checkStack(node, stackBase, [
/* expression = */ ValueKinds.Expression,
]));
Pattern? replacement;
InvalidExpression? error =
analysisResult.refutablePatternInIrrefutableContextError ??
analysisResult.operatorReturnTypeNotAssignableToBoolError ??
analysisResult.argumentTypeNotAssignableError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
Object? rewrite = popRewrite();
if (!identical(rewrite, node.expression)) {
node.expression = (rewrite as Expression)..parent = node;
}
DartType expressionType = analysisResult.operandType;
node.expressionType = expressionType;
Name name;
switch (node.kind) {
case RelationalPatternKind.equals:
case RelationalPatternKind.notEquals:
name = node.name = equalsName;
break;
case RelationalPatternKind.lessThan:
name = node.name = lessThanName;
break;
case RelationalPatternKind.lessThanEqual:
name = node.name = lessThanOrEqualsName;
break;
case RelationalPatternKind.greaterThan:
name = node.name = greaterThanName;
break;
case RelationalPatternKind.greaterThanEqual:
name = node.name = greaterThanOrEqualsName;
break;
}
ObjectAccessTarget invokeTarget = findInterfaceMember(
matchedValueType, name, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
switch (node.kind) {
case RelationalPatternKind.equals:
case RelationalPatternKind.notEquals:
assert(invokeTarget.isInstanceMember ||
invokeTarget.isObjectMember ||
invokeTarget.isNever);
node.functionType = invokeTarget.getFunctionType(this);
node.accessKind = RelationalAccessKind.Instance;
node.target = invokeTarget.classMember as Procedure;
break;
case RelationalPatternKind.lessThan:
case RelationalPatternKind.lessThanEqual:
case RelationalPatternKind.greaterThan:
case RelationalPatternKind.greaterThanEqual:
switch (invokeTarget.kind) {
case ObjectAccessTargetKind.instanceMember:
node.functionType = invokeTarget.getFunctionType(this);
node.target = invokeTarget.classMember as Procedure;
node.accessKind = RelationalAccessKind.Instance;
break;
case ObjectAccessTargetKind.nullableInstanceMember:
case ObjectAccessTargetKind.nullableExtensionMember:
case ObjectAccessTargetKind.nullableExtensionTypeMember:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
replacement ??= new InvalidPattern(
createMissingMethodInvocation(
node.fileOffset, matchedValueType, name,
isExpressionInvocation: false),
declaredVariables: node.declaredVariables)
..fileOffset = node.fileOffset;
break;
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.superMember:
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.recordIndexed:
case ObjectAccessTargetKind.recordNamed:
case ObjectAccessTargetKind.nullableRecordIndexed:
case ObjectAccessTargetKind.nullableRecordNamed:
case ObjectAccessTargetKind.extensionTypeRepresentation:
case ObjectAccessTargetKind.nullableExtensionTypeRepresentation:
problems.unsupported('Relational pattern target $invokeTarget',
node.fileOffset, helper.uri);
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.extensionTypeMember:
node.functionType = invokeTarget.getFunctionType(this);
node.typeArguments = invokeTarget.receiverTypeArguments;
node.target = invokeTarget.member as Procedure;
node.accessKind = RelationalAccessKind.Static;
break;
case ObjectAccessTargetKind.dynamic:
node.accessKind = RelationalAccessKind.Dynamic;
break;
case ObjectAccessTargetKind.never:
node.accessKind = RelationalAccessKind.Never;
break;
case ObjectAccessTargetKind.invalid:
node.accessKind = RelationalAccessKind.Invalid;
break;
}
break;
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitMapPattern(
MapPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
({DartType keyType, DartType valueType})? typeArguments =
node.keyType == null && node.valueType == null
? null
: (
keyType: node.keyType ?? const DynamicType(),
valueType: node.valueType ?? const DynamicType()
);
MapPatternResult<DartType, InvalidExpression> analysisResult =
analyzeMapPattern(context, node,
typeArguments: typeArguments, elements: node.entries);
DartType matchedValueType =
node.matchedValueType = analysisResult.matchedValueType;
Pattern? replacement;
InvalidExpression? error =
analysisResult.patternTypeMismatchInIrrefutableContextError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
error = analysisResult.emptyMapPatternError;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
// TODO(johnniwinther): The required type computed by the type analyzer
// isn't trivially `Map<dynamic, dynamic>` in all cases. Does that matter
// for the lowering?
DartType requiredType = node.requiredType = analysisResult.requiredType;
node.needsCheck =
_needsCheck(matchedType: matchedValueType, requiredType: requiredType);
DartType lookupType;
if (node.needsCheck) {
lookupType = node.lookupType = requiredType;
} else {
lookupType = node.lookupType = matchedValueType;
}
ObjectAccessTarget containsKeyTarget = findInterfaceMember(
lookupType, containsKeyName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(containsKeyTarget.isInstanceMember);
node.containsKeyTarget = containsKeyTarget.classMember as Procedure;
node.containsKeyType = containsKeyTarget.getFunctionType(this);
ObjectAccessTarget indexGetTarget = findInterfaceMember(
lookupType, indexGetName, node.fileOffset,
includeExtensionMethods: true, isSetter: false);
assert(indexGetTarget.isInstanceMember);
node.indexGetTarget = indexGetTarget.classMember as Procedure;
node.indexGetType = indexGetTarget.getFunctionType(this);
assert(checkStack(node, stackBase, [
/* entries = */ ...repeatedKind(
ValueKinds.MapPatternEntry, node.entries.length)
]));
for (int i = node.entries.length - 1; i >= 0; i--) {
Object? rewrite = popRewrite();
if (!identical(node.entries[i], rewrite)) {
node.entries[i] = (rewrite as MapPatternEntry)..parent = node;
}
}
Map<int, InvalidExpression>? restPatternErrors =
analysisResult.restPatternErrors;
if (restPatternErrors != null) {
InvalidExpression? firstError;
int insertionIndex = 0;
for (int readIndex = 0; readIndex < node.entries.length; readIndex++) {
InvalidExpression? error = restPatternErrors[readIndex];
if (error != null) {
firstError ??= error;
} else {
node.entries[insertionIndex++] = node.entries[readIndex];
}
}
node.entries.length = insertionIndex;
if (insertionIndex == 0) {
replacement ??= new InvalidPattern(firstError!,
declaredVariables: node.declaredVariables)
..fileOffset = node.fileOffset;
}
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
PatternResult<DartType> visitNamedPattern(
NamedPattern node, SharedMatchContext context) {
// NamedPattern isn't a real pattern; this code should never be reached.
throw new StateError('visitNamedPattern should never be reached');
}
@override
PatternResult<DartType> visitRecordPattern(
RecordPattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
List<RecordPatternField<TreeNode, Pattern>> fields = [
for (Pattern fieldPattern in node.patterns)
new RecordPatternField(
node: fieldPattern,
pattern: fieldPattern is NamedPattern
? fieldPattern.pattern
: fieldPattern,
name: fieldPattern is NamedPattern ? fieldPattern.name : null)
];
RecordPatternResult<DartType, InvalidExpression> analysisResult =
analyzeRecordPattern(context, node, fields: fields);
DartType matchedValueType =
node.matchedValueType = analysisResult.matchedValueType;
assert(checkStack(node, stackBase, [
/* fields = */ ...repeatedKind(ValueKinds.Pattern, node.patterns.length)
]));
Pattern? replacement;
InvalidExpression? error =
analysisResult.patternTypeMismatchInIrrefutableContextError ??
analysisResult.duplicateRecordPatternFieldErrors?.values.first;
if (error != null) {
replacement =
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
RecordType requiredType =
node.requiredType = analysisResult.requiredType as RecordType;
// TODO(johnniwinther): How does `recordType` relate to `node.recordType`?
node.needsCheck =
_needsCheck(matchedType: matchedValueType, requiredType: requiredType);
if (node.needsCheck) {
node.lookupType = requiredType;
} else {
DartType resolvedType = matchedValueType.nonTypeVariableBound;
if (resolvedType is RecordType) {
node.lookupType = resolvedType;
} else {
// In case of the matched type being an invalid type we use the
// required type instead.
node.lookupType = requiredType;
}
}
for (int i = node.patterns.length - 1; i >= 0; i--) {
Pattern subPattern = node.patterns[i];
Object? rewrite = popRewrite();
if (subPattern is NamedPattern) {
if (!identical(rewrite, subPattern.pattern)) {
subPattern.pattern = (rewrite as Pattern)..parent = subPattern;
}
} else {
if (!identical(rewrite, subPattern)) {
node.patterns[i] = (rewrite as Pattern)..parent = node;
}
}
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
@override
ExpressionInferenceResult visitPatternAssignment(
PatternAssignment node, DartType typeContext) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
PatternAssignmentAnalysisResult<DartType, DartType> analysisResult =
analyzePatternAssignment(node, node.pattern, node.expression);
node.matchedValueType = analysisResult.type;
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
/* expression = */ ValueKinds.Expression,
]));
Object? rewrite = popRewrite();
if (!identical(node.pattern, rewrite)) {
node.pattern = rewrite as Pattern..parent = node;
}
assert(checkStack(node, stackBase, [
/* expression = */ ValueKinds.Expression,
]));
rewrite = popRewrite();
if (!identical(node.expression, rewrite)) {
node.expression = rewrite as Expression..parent = node;
}
assert(checkStack(node, stackBase, [/*empty*/]));
return new ExpressionInferenceResult(
analysisResult.resolveShorting(), node);
}
@override
PatternResult<DartType> visitAssignedVariablePattern(
AssignedVariablePattern node, SharedMatchContext context) {
int? stackBase;
assert(checkStackBase(node, stackBase = stackHeight));
// TODO(johnniwinther): Share this through the type analyzer.
Pattern? replacement;
VariableDeclarationImpl variable = node.variable as VariableDeclarationImpl;
if (isNonNullableByDefault) {
bool isDefinitelyAssigned = flowAnalysis.isAssigned(variable);
bool isDefinitelyUnassigned = flowAnalysis.isUnassigned(variable);
if ((variable.isLate && variable.isFinal) ||
variable.isLateFinalWithoutInitializer) {
if (isDefinitelyAssigned) {
replacement = new InvalidPattern(
helper.buildProblem(
templateLateDefinitelyAssignedError
.withArguments(node.variable.name!),
node.fileOffset,
node.variable.name!.length),
declaredVariables: node.declaredVariables)
..fileOffset = node.fileOffset;
}
} else if (variable.isStaticLate) {
if (!isDefinitelyUnassigned) {
replacement = new InvalidPattern(
helper.buildProblem(
templateFinalPossiblyAssignedError
.withArguments(node.variable.name!),
node.fileOffset,
node.variable.name!.length),
declaredVariables: node.declaredVariables)
..fileOffset = node.fileOffset;
}
} else if (variable.isFinal && variable.hasDeclaredInitializer) {
replacement = new InvalidPattern(
helper.buildProblem(
templateCannotAssignToFinalVariable
.withArguments(node.variable.name!),
node.fileOffset,
node.variable.name!.length),
declaredVariables: node.declaredVariables)
..fileOffset = node.fileOffset;
}
}
AssignedVariablePatternResult<DartType, InvalidExpression> analysisResult =
analyzeAssignedVariablePattern(context, node, node.variable);
DartType matchedValueType =
node.matchedValueType = analysisResult.matchedValueType;
node.needsCast = _needsCast(
matchedType: matchedValueType, requiredType: node.variable.type);
node.hasObservableEffect = _inTryOrLocalFunction;
InvalidExpression? error =
analysisResult.duplicateAssignmentPatternVariableError ??
analysisResult.patternTypeMismatchInIrrefutableContextError;
if (error != null) {
replacement ??=
new InvalidPattern(error, declaredVariables: node.declaredVariables)
..fileOffset = error.fileOffset;
}
pushRewrite(replacement ?? node);
assert(checkStack(node, stackBase, [
/* pattern = */ ValueKinds.Pattern,
]));
return analysisResult;
}
/// Infers type arguments corresponding to [typeParameters] so that, when
/// substituted into [declaredType], the resulting type matches [contextType].
List<DartType> _inferTypeArguments(
{required List<TypeParameter> typeParameters,
required DartType declaredType,
required DartType contextType,
required TreeNode? treeNodeForTesting}) {
FreshStructuralParametersFromTypeParameters freshTypeParameters =
getFreshStructuralParametersFromTypeParameters(typeParameters);
List<StructuralParameter> typeParametersToInfer =
freshTypeParameters.freshTypeParameters;
declaredType = freshTypeParameters.substitute(declaredType);
TypeConstraintGatherer gatherer =
typeSchemaEnvironment.setupGenericTypeInference(
declaredType, typeParametersToInfer, contextType,
isNonNullableByDefault: isNonNullableByDefault,
typeOperations: operations,
inferenceResultForTesting: dataForTesting?.typeInferenceResult,
treeNodeForTesting: treeNodeForTesting);
return typeSchemaEnvironment.chooseFinalTypes(
gatherer, typeParametersToInfer, null,
isNonNullableByDefault: isNonNullableByDefault);
}
@override
DartType downwardInferObjectPatternRequiredType({
required DartType matchedType,
required covariant ObjectPatternInternal pattern,
}) {
DartType requiredType = pattern.requiredType;
if (!pattern.hasExplicitTypeArguments) {
Typedef? typedef = pattern.typedef;
if (typedef != null) {
List<TypeParameter> typedefTypeParameters = typedef.typeParameters;
if (typedefTypeParameters.isNotEmpty) {
List<DartType> asTypeArguments =
getAsTypeArguments(typedefTypeParameters, libraryBuilder.library);
TypedefType typedefType = new TypedefType(
typedef, libraryBuilder.library.nonNullable, asTypeArguments);
DartType unaliasedTypedef = typedefType.unalias;
List<DartType> inferredTypeArguments = _inferTypeArguments(
typeParameters: typedefTypeParameters,
declaredType: unaliasedTypedef,
contextType: matchedType,
treeNodeForTesting: pattern);
requiredType = new TypedefType(typedef,
libraryBuilder.library.nonNullable, inferredTypeArguments)
.unalias;
}
} else if (requiredType is InterfaceType) {
List<TypeParameter> typeParameters =
requiredType.classNode.typeParameters;
if (typeParameters.isNotEmpty) {
// It's possible that one of the callee type parameters might match a
// type that already exists as part of inference. This might happen,
// for instance, in the case where a method in a generic class
// contains an object pattern naming the enclosing class. To avoid
// creating invalid inference results, we need to create fresh type
// parameters.
FreshTypeParameters fresh = getFreshTypeParameters(typeParameters);
InterfaceType declaredType = new InterfaceType(requiredType.classNode,
requiredType.declaredNullability, fresh.freshTypeArguments);
typeParameters = fresh.freshTypeParameters;
List<DartType> inferredTypeArguments = _inferTypeArguments(
typeParameters: typeParameters,
declaredType: declaredType,
contextType: matchedType,
treeNodeForTesting: pattern);
requiredType = new InterfaceType(requiredType.classNode,
requiredType.declaredNullability, inferredTypeArguments);
}
} else if (requiredType is ExtensionType) {
List<TypeParameter> typeParameters =
requiredType.extensionTypeDeclaration.typeParameters;
if (typeParameters.isNotEmpty) {
// It's possible that one of the callee type parameters might match a
// type that already exists as part of inference. This might happen,
// for instance, in the case where a method in a generic class
// contains an object pattern naming the enclosing class. To avoid
// creating invalid inference results, we need to create fresh type
// parameters.
FreshTypeParameters fresh = getFreshTypeParameters(typeParameters);
ExtensionType declaredType = new ExtensionType(
requiredType.extensionTypeDeclaration,
requiredType.declaredNullability,
fresh.freshTypeArguments);
typeParameters = fresh.freshTypeParameters;
List<DartType> inferredTypeArguments = _inferTypeArguments(
typeParameters: typeParameters,
declaredType: declaredType,
contextType: matchedType,
treeNodeForTesting: pattern);
requiredType = new ExtensionType(
requiredType.extensionTypeDeclaration,
requiredType.declaredNullability,
inferredTypeArguments);
}
}
}
return requiredType;
}
@override
void dispatchCollectionElement(covariant TreeNode element,
covariant CollectionElementInferenceContext context) {
if (element is Expression) {
context as ListAndSetElementInferenceContext;
ExpressionInferenceResult inferenceResult = inferElement(
element,
context.inferredTypeArgument,
context.inferredSpreadTypes,
context.inferredConditionTypes);
// TODO(cstefantsova): Should the key to the map be [element] instead?
context.inferredConditionTypes[inferenceResult.expression] =
inferenceResult.inferredType;
pushRewrite(inferenceResult.expression);
} else if (element is MapLiteralEntry) {
context as MapEntryInferenceContext;
element = inferMapEntry(
element,
element.parent!,
context.inferredKeyType,
context.inferredValueType,
context.spreadContext,
context.actualTypes,
context.actualTypesForSet,
context.inferredSpreadTypes,
context.inferredConditionTypes,
context.offsets);
pushRewrite(element);
} else {
problems.unsupported(
"${element.runtimeType}", element.fileOffset, helper.uri);
}
}
@override
(Member?, DartType) resolveObjectPatternPropertyGet({
required Pattern objectPattern,
required DartType receiverType,
required shared.RecordPatternField<TreeNode, Pattern> field,
}) {
String fieldName = field.name!;
ObjectAccessTarget fieldAccessTarget = findInterfaceMember(receiverType,
new Name(fieldName, libraryBuilder.library), field.pattern.fileOffset,
isSetter: false, includeExtensionMethods: true);
// TODO(johnniwinther): Should we use the `fieldAccessTarget.classMember`
// here?
return (fieldAccessTarget.member, fieldAccessTarget.getGetterType(this));
}
@override
void handleNoCollectionElement(TreeNode element) {
pushRewrite(NullValues.Expression);
}
@override
void finishJoinedPatternVariable(
VariableDeclaration variable, {
required JoinedPatternVariableLocation location,
required JoinedPatternVariableInconsistency inconsistency,
required bool isFinal,
required DartType type,
}) {
variable
..isFinal = isFinal
..type = type;
}
@override
bool isRestPatternElement(Node node) {
return node is RestPattern || node is MapPatternRestEntry;
}
@override
Pattern? getRestPatternElementPattern(TreeNode node) {
if (node is MapPatternRestEntry) {
return null;
} else {
return (node as RestPattern).subPattern;
}
}
@override
void handleListPatternRestElement(Pattern container, TreeNode restElement) {
RestPattern restPattern = restElement as RestPattern;
int? stackBase;
if (restPattern.subPattern != null) {
assert(checkStackBase(restPattern, stackBase = stackHeight - 1));
assert(checkStack(
restPattern, stackBase, [/* subpattern = */ ValueKinds.Pattern]));
Object? rewrite = popRewrite();
if (!identical(rewrite, restPattern.subPattern)) {
restPattern.subPattern = (rewrite as Pattern)..parent = restPattern;
}
} else {
assert(checkStackBase(restPattern, stackBase = stackHeight));
}
assert(checkStack(restPattern, stackBase, [/*empty*/]));
pushRewrite(restElement);
assert(checkStack(
restPattern, stackBase, [/* rest pattern = */ ValueKinds.Pattern]));
}
@override
void handleMapPatternRestElement(Pattern container, TreeNode restElement) {
pushRewrite(restElement);
}
@override
shared.MapPatternEntry<Expression, Pattern>? getMapPatternEntry(
TreeNode element) {
element as MapPatternEntry;
if (element is MapPatternRestEntry) {
return null;
} else {
return new shared.MapPatternEntry<Expression, Pattern>(
key: element.key, value: element.value);
}
}
@override
void handleMapPatternEntry(Pattern container,
covariant MapPatternEntry entryElement, DartType keyType) {
Object? rewrite = popRewrite();
if (!identical(rewrite, entryElement.value)) {
entryElement.value = rewrite as Pattern..parent = entryElement;
}
rewrite = popRewrite();
if (!identical(rewrite, entryElement.key)) {
entryElement.key = (rewrite as Expression)..parent = entryElement;
}
entryElement.keyType = keyType;
pushRewrite(entryElement);
}
@override
RelationalOperatorResolution<DartType>? resolveRelationalPatternOperator(
covariant RelationalPattern node, DartType matchedValueType) {
// TODO(johnniwinther): Reuse computed values between here and
// visitRelationalPattern.
Name operatorName;
RelationalOperatorKind kind = RelationalOperatorKind.other;
switch (node.kind) {
case RelationalPatternKind.equals:
operatorName = equalsName;
kind = RelationalOperatorKind.equals;
break;
case RelationalPatternKind.notEquals:
operatorName = equalsName;
kind = RelationalOperatorKind.notEquals;
break;
case RelationalPatternKind.lessThan:
operatorName = lessThanName;
break;
case RelationalPatternKind.lessThanEqual:
operatorName = lessThanOrEqualsName;
break;
case RelationalPatternKind.greaterThan:
operatorName = greaterThanName;
break;
case RelationalPatternKind.greaterThanEqual:
operatorName = greaterThanOrEqualsName;
break;
}
ObjectAccessTarget binaryTarget = findInterfaceMember(
matchedValueType, operatorName, node.fileOffset,
isSetter: false);
DartType returnType = binaryTarget.getReturnType(this);
DartType parameterType = binaryTarget.getBinaryOperandType(this);
assert(!binaryTarget.isSpecialCasedBinaryOperator(this));
return new RelationalOperatorResolution(
kind: kind, parameterType: parameterType, returnType: returnType);
}
@override
ExpressionInferenceResult visitAuxiliaryExpression(
AuxiliaryExpression node, DartType typeContext) {
return _unhandledExpression(node, typeContext);
}
@override
InitializerInferenceResult visitAuxiliaryInitializer(
AuxiliaryInitializer node) {
if (node is InternalInitializer) {
return node.acceptInference(this);
}
return _unhandledInitializer(node);
}
@override
StatementInferenceResult visitAuxiliaryStatement(AuxiliaryStatement node) {
return _unhandledStatement(node);
}
}
/// Offset and type information collection in [InferenceVisitor.inferMapEntry].
class _MapLiteralEntryOffsets {
// Stores the offset of the map entry found by inferMapEntry.
int? mapEntryOffset;
// Stores the offset of the map spread found by inferMapEntry.
int? mapSpreadOffset;
// Stores the offset of the iterable spread found by inferMapEntry.
int? iterableSpreadOffset;
// Stores the type of the iterable spread found by inferMapEntry.
DartType? iterableSpreadType;
}
extension on SwitchCase {
int get caseHeadCount {
int count = 0;
if (this is PatternSwitchCase) {
count += (this as PatternSwitchCase).patternGuards.length;
} else {
count += this.expressions.length;
}
return count;
}
}
abstract class CollectionElementInferenceContext {
Map<TreeNode, DartType> inferredSpreadTypes;
Map<Expression, DartType> inferredConditionTypes;
CollectionElementInferenceContext(
{required this.inferredSpreadTypes,
required this.inferredConditionTypes});
}
class ListAndSetElementInferenceContext
extends CollectionElementInferenceContext {
DartType inferredTypeArgument;
ListAndSetElementInferenceContext(
{required this.inferredTypeArgument,
required Map<TreeNode, DartType> inferredSpreadTypes,
required Map<Expression, DartType> inferredConditionTypes})
: super(
inferredSpreadTypes: inferredSpreadTypes,
inferredConditionTypes: inferredConditionTypes);
}
class MapEntryInferenceContext extends CollectionElementInferenceContext {
DartType inferredKeyType;
DartType inferredValueType;
DartType spreadContext;
List<DartType> actualTypes;
List<DartType> actualTypesForSet;
_MapLiteralEntryOffsets offsets;
MapEntryInferenceContext(
{required this.inferredKeyType,
required this.inferredValueType,
required this.spreadContext,
required this.actualTypes,
required this.actualTypesForSet,
required this.offsets,
required Map<TreeNode, DartType> inferredSpreadTypes,
required Map<Expression, DartType> inferredConditionTypes})
: super(
inferredSpreadTypes: inferredSpreadTypes,
inferredConditionTypes: inferredConditionTypes);
}