blob: 73e692a864b872b27e8bea8dce75ee5e30793336 [file]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/names.dart';
import '../kernel/external_ast_helper.dart';
import 'delayed_expressions.dart';
import 'matching_cache.dart';
/// Visitor that creates the [DelayedExpression] needed to match expressions,
/// using [MatchingCache] to create cacheable expressions.
class MatchingExpressionVisitor
implements PatternVisitor1<DelayedExpression, CacheableExpression> {
final MatchingCache matchingCache;
final CoreTypes coreTypes;
new(this.matchingCache, this.coreTypes);
DelayedExpression visitPattern(
Pattern node,
CacheableExpression matchedExpression,
) {
return node.accept1(this, matchedExpression);
}
@override
DelayedExpression visitAndPattern(
AndPattern node,
CacheableExpression matchedExpression,
) {
return new DelayedAndExpression(
visitPattern(node.left, matchedExpression),
visitPattern(node.right, matchedExpression),
fileOffset: node.fileOffset,
);
}
@override
DelayedExpression visitAssignedVariablePattern(
AssignedVariablePattern node,
CacheableExpression matchedExpression,
) {
matchedExpression = matchedExpression.promote(node.matchedValueType!);
DelayedExpression valueExpression;
if (node.needsCast) {
valueExpression = new DelayedAsExpression(
matchedExpression,
node.variable.type,
fileOffset: node.fileOffset,
);
} else {
valueExpression = matchedExpression;
}
return new DelayedAssignment(
matchingCache,
node.setter ?? node.variable,
node.variable.type,
valueExpression,
fileOffset: node.fileOffset,
hasEffect: node.hasObservableEffect,
);
}
@override
DelayedExpression visitCastPattern(
CastPattern node,
CacheableExpression matchedExpression,
) {
CacheableExpression asExpression = matchingCache.createAsExpression(
matchedExpression,
node.type,
fileOffset: node.fileOffset,
);
return new EffectExpression(
asExpression,
visitPattern(node.pattern, asExpression),
);
}
@override
DelayedExpression visitConstantPattern(
ConstantPattern node,
CacheableExpression matchedExpression,
) {
CacheableExpression constExpression = matchingCache
.createConstantExpression(
node.value!,
node.expressionType!,
fileOffset: node.fileOffset,
);
return matchingCache.createEqualsExpression(
constExpression,
matchedExpression,
new DelayedEqualsExpression(
constExpression,
matchedExpression,
node.equalsTarget,
node.equalsType!,
fileOffset: node.fileOffset,
),
fileOffset: node.fileOffset,
);
}
@override
DelayedExpression visitInvalidPattern(
InvalidPattern node,
CacheableExpression matchedExpression,
) {
return new FixedExpression(node.invalidExpression, const InvalidType());
}
@override
DelayedExpression visitListPattern(
ListPattern node,
CacheableExpression matchedExpression,
) {
matchedExpression = matchedExpression.promote(node.matchedValueType!);
CacheableExpression? isExpression;
CacheableExpression typedMatchedExpression;
if (node.needsCheck) {
isExpression = matchingCache.createIsExpression(
matchedExpression,
node.requiredType!,
fileOffset: node.fileOffset,
);
typedMatchedExpression = new PromotedCacheableExpression(
matchedExpression,
node.requiredType!,
);
} else {
typedMatchedExpression = matchedExpression;
}
CacheableExpression lengthGet = matchingCache.createPropertyGetExpression(
typedMatchedExpression,
lengthName.text,
new DelayedInstanceGet(
typedMatchedExpression,
node.lengthTarget,
node.lengthType!,
fileOffset: node.fileOffset,
),
fileOffset: node.fileOffset,
);
CacheableExpression? lengthCheck;
if (node.hasRestPattern) {
int minLength = node.patterns.length - 1;
if (minLength > 0) {
CacheableExpression constExpression = matchingCache.createIntConstant(
minLength,
fileOffset: node.fileOffset,
);
lengthCheck = matchingCache.createComparisonExpression(
lengthGet,
greaterThanOrEqualsName.text,
constExpression,
new DelayedInstanceInvocation(
lengthGet,
node.lengthCheckTarget,
node.lengthCheckType!,
[constExpression],
fileOffset: node.fileOffset,
),
fileOffset: node.fileOffset,
);
}
} else {
int length = node.patterns.length;
CacheableExpression constExpression = matchingCache.createIntConstant(
length,
fileOffset: node.fileOffset,
);
if (length == 0) {
lengthCheck = matchingCache.createComparisonExpression(
lengthGet,
lessThanOrEqualsName.text,
constExpression,
new DelayedInstanceInvocation(
lengthGet,
node.lengthCheckTarget,
node.lengthCheckType!,
[constExpression],
fileOffset: node.fileOffset,
),
fileOffset: node.fileOffset,
);
} else {
lengthCheck = matchingCache.createEqualsExpression(
lengthGet,
constExpression,
new DelayedEqualsExpression(
lengthGet,
constExpression,
node.lengthCheckTarget,
node.lengthCheckType!,
fileOffset: node.fileOffset,
),
fileOffset: node.fileOffset,
);
}
}
DelayedExpression? matchingExpression;
if (isExpression != null && lengthCheck != null) {
matchingExpression = matchingCache.createAndExpression(
isExpression,
lengthCheck,
fileOffset: node.fileOffset,
);
} else if (isExpression != null) {
matchingExpression = isExpression;
} else if (lengthCheck != null) {
matchingExpression = lengthCheck;
}
bool hasSeenRestPattern = false;
for (int i = 0; i < node.patterns.length; i++) {
CacheableExpression elementExpression;
Pattern elementPattern = node.patterns[i];
if (elementPattern is RestPattern) {
hasSeenRestPattern = true;
Pattern? subPattern = elementPattern.subPattern;
if (subPattern == null) {
continue;
}
int nextIndex = i + 1;
int headSize = i;
int tailSize = node.patterns.length - nextIndex;
DelayedExpression expression;
if (tailSize > 0) {
expression = new DelayedInstanceInvocation(
typedMatchedExpression,
node.sublistTarget,
node.sublistType!,
[
new IntegerExpression(headSize, fileOffset: node.fileOffset),
new DelayedInstanceInvocation(
lengthGet,
node.minusTarget,
node.minusType!,
[new IntegerExpression(tailSize, fileOffset: node.fileOffset)],
fileOffset: node.fileOffset,
),
],
fileOffset: node.fileOffset,
);
} else {
expression = new DelayedInstanceInvocation(
typedMatchedExpression,
node.sublistTarget,
node.sublistType!,
[new IntegerExpression(headSize, fileOffset: node.fileOffset)],
fileOffset: node.fileOffset,
);
}
elementExpression = matchingCache.createSublistExpression(
typedMatchedExpression,
lengthGet,
headSize,
tailSize,
expression,
fileOffset: node.fileOffset,
);
} else {
if (!hasSeenRestPattern) {
int index = i;
elementExpression = matchingCache.createHeadIndexExpression(
typedMatchedExpression,
index,
new DelayedInstanceInvocation(
typedMatchedExpression,
node.indexGetTarget,
node.indexGetType!,
[new IntegerExpression(index, fileOffset: node.fileOffset)],
fileOffset: node.fileOffset,
),
fileOffset: node.fileOffset,
);
} else {
int index = node.patterns.length - i;
elementExpression = matchingCache.createTailIndexExpression(
typedMatchedExpression,
lengthGet,
index,
new DelayedInstanceInvocation(
typedMatchedExpression,
node.indexGetTarget,
node.indexGetType!,
[
new DelayedInstanceInvocation(
lengthGet,
node.minusTarget,
node.minusType!,
[new IntegerExpression(index, fileOffset: node.fileOffset)],
fileOffset: node.fileOffset,
),
],
fileOffset: node.fileOffset,
),
fileOffset: node.fileOffset,
);
}
}
DelayedExpression elementMatcher = visitPattern(
elementPattern,
elementExpression,
);
matchingExpression = DelayedAndExpression.merge(
matchingExpression,
elementMatcher,
fileOffset: node.fileOffset,
);
}
return matchingExpression ??
new BooleanExpression(true, fileOffset: node.fileOffset);
}
@override
DelayedExpression visitMapPattern(
MapPattern node,
CacheableExpression matchedExpression,
) {
matchedExpression = matchedExpression.promote(node.matchedValueType!);
CacheableExpression? isExpression;
CacheableExpression typedMatchedExpression;
if (node.needsCheck) {
isExpression = matchingCache.createIsExpression(
matchedExpression,
node.requiredType!,
fileOffset: node.fileOffset,
);
typedMatchedExpression = new PromotedCacheableExpression(
matchedExpression,
node.requiredType!,
);
} else {
typedMatchedExpression = matchedExpression;
}
DelayedExpression? matchingExpression;
if (isExpression != null) {
matchingExpression = isExpression;
}
InterfaceType requiredType = node.requiredType as InterfaceType;
assert(
requiredType.classNode == coreTypes.mapClass &&
requiredType.typeArguments.length == 2,
);
DartType valueType = requiredType.typeArguments[1];
for (MapPatternEntry entry in node.entries) {
if (entry is MapPatternRestEntry) continue;
CacheableExpression keyExpression = matchingCache
.createConstantExpression(
entry.keyValue!,
entry.keyType!,
fileOffset: entry.key.fileOffset,
);
CacheableExpression containsExpression = matchingCache
.createContainsKeyExpression(
typedMatchedExpression,
keyExpression,
new DelayedInstanceInvocation(
typedMatchedExpression,
node.containsKeyTarget,
node.containsKeyType!,
[keyExpression],
fileOffset: entry.fileOffset,
),
fileOffset: entry.fileOffset,
);
CacheableExpression valueExpression = matchingCache.createIndexExpression(
typedMatchedExpression,
keyExpression,
new DelayedInstanceInvocation(
typedMatchedExpression,
node.indexGetTarget,
node.indexGetType!,
[keyExpression],
fileOffset: entry.fileOffset,
),
fileOffset: entry.fileOffset,
);
matchingExpression = DelayedAndExpression.merge(
matchingExpression,
new DelayedOrExpression(
new DelayedNullCheckExpression(
valueExpression,
fileOffset: entry.fileOffset,
),
new DelayedAndExpression(
new DelayedIsExpression(
new FixedExpression(
createNullLiteral(fileOffset: entry.fileOffset),
const NullType(),
),
valueType,
fileOffset: entry.fileOffset,
),
containsExpression,
fileOffset: entry.fileOffset,
),
fileOffset: entry.fileOffset,
),
fileOffset: entry.fileOffset,
);
valueExpression = new PromotedCacheableExpression(
valueExpression,
valueType,
);
DelayedExpression subExpression = visitPattern(
entry.value,
valueExpression,
);
if (!subExpression.uses(valueExpression)) {
// Ensure that we perform the lookup even if we don't use the result.
matchingExpression = DelayedAndExpression.merge(
matchingExpression,
new EffectExpression(valueExpression, subExpression),
fileOffset: node.fileOffset,
);
} else {
matchingExpression = DelayedAndExpression.merge(
matchingExpression,
subExpression,
fileOffset: node.fileOffset,
);
}
}
// Coverage-ignore(suite): Not run.
return matchingExpression ??
new BooleanExpression(true, fileOffset: node.fileOffset);
}
@override
DelayedExpression visitNamedPattern(
NamedPattern node,
CacheableExpression matchedExpression,
) {
return visitPattern(node.pattern, matchedExpression);
}
@override
DelayedExpression visitNullAssertPattern(
NullAssertPattern node,
CacheableExpression matchedExpression,
) {
CacheableExpression nullAssertExpression = matchingCache
.createNullAssertMatcher(
matchedExpression,
fileOffset: node.fileOffset,
);
return new EffectExpression(
nullAssertExpression,
visitPattern(node.pattern, nullAssertExpression),
);
}
@override
DelayedExpression visitNullCheckPattern(
NullCheckPattern node,
CacheableExpression matchedExpression,
) {
CacheableExpression nullCheckExpression = matchingCache
.createNullCheckMatcher(matchedExpression, fileOffset: node.fileOffset);
return new DelayedAndExpression(
nullCheckExpression,
visitPattern(node.pattern, matchedExpression),
fileOffset: node.fileOffset,
);
}
@override
DelayedExpression visitObjectPattern(
ObjectPattern node,
CacheableExpression matchedExpression,
) {
matchedExpression = matchedExpression.promote(node.matchedValueType!);
DelayedExpression? matchingExpression;
CacheableExpression typedMatchedExpression;
if (node.needsCheck) {
matchingExpression = matchingCache.createIsExpression(
matchedExpression,
node.requiredType,
fileOffset: node.fileOffset,
);
typedMatchedExpression = new PromotedCacheableExpression(
matchedExpression,
node.requiredType,
);
} else {
typedMatchedExpression = matchedExpression;
}
for (NamedPattern field in node.fields) {
DelayedExpression expression;
Member? staticTarget;
List<DartType>? typeArguments;
switch (field.accessKind) {
case ObjectAccessKind.Object:
assert(!field.checkReturn);
expression = new DelayedInstanceGet(
typedMatchedExpression,
field.target!,
field.resultType!,
isObjectAccess: true,
fileOffset: field.fileOffset,
);
break;
case ObjectAccessKind.Instance:
expression = new DelayedInstanceGet(
typedMatchedExpression,
field.target!,
field.resultType!,
isObjectAccess: false,
fileOffset: field.fileOffset,
covarianceCheckTypes: field.checkReturn
? new CovarianceCheckTypes(
operandStaticType: coreTypes.objectNullableRawType,
checkedType: field.resultType!,
)
: null,
);
break;
case ObjectAccessKind.Direct:
assert(!field.checkReturn);
expression = new DelayedAsExpression(
typedMatchedExpression,
field.resultType!,
isUnchecked: true,
fileOffset: field.fileOffset,
);
case ObjectAccessKind.Extension:
case ObjectAccessKind.ExtensionType:
assert(!field.checkReturn);
expression = new DelayedExtensionInvocation(
field.target as Procedure,
[typedMatchedExpression],
field.typeArguments!,
field.resultType!,
fileOffset: field.fileOffset,
);
staticTarget = field.target;
typeArguments = field.typeArguments;
break;
case ObjectAccessKind.RecordNamed:
assert(!field.checkReturn);
expression = new DelayedRecordNameGet(
typedMatchedExpression,
field.recordType!,
field.fieldName.text,
fileOffset: field.fileOffset,
);
break;
case ObjectAccessKind.RecordIndexed:
assert(!field.checkReturn);
expression = new DelayedRecordIndexGet(
typedMatchedExpression,
field.recordType!,
field.recordFieldIndex,
fileOffset: field.fileOffset,
);
break;
case ObjectAccessKind.Dynamic:
assert(!field.checkReturn);
expression = new DelayedDynamicGet(
typedMatchedExpression,
field.fieldName,
DynamicAccessKind.Dynamic,
const DynamicType(),
fileOffset: field.fileOffset,
);
break;
case ObjectAccessKind.Never:
assert(!field.checkReturn);
expression = new DelayedDynamicGet(
typedMatchedExpression,
field.fieldName,
DynamicAccessKind.Never,
const NeverType.nonNullable(),
fileOffset: field.fileOffset,
);
break;
case ObjectAccessKind.Invalid:
assert(!field.checkReturn);
expression = new DelayedDynamicGet(
typedMatchedExpression,
field.fieldName,
DynamicAccessKind.Invalid,
const InvalidType(),
fileOffset: field.fileOffset,
);
break;
case ObjectAccessKind.FunctionTearOff:
assert(!field.checkReturn);
expression = new DelayedFunctionTearOff(
typedMatchedExpression,
node.requiredType,
fileOffset: field.fileOffset,
);
break;
case ObjectAccessKind.Error:
assert(!field.checkReturn);
expression = new FixedExpression(
(field.pattern as InvalidPattern).invalidExpression,
const InvalidType(),
);
break;
}
CacheableExpression objectExpression = matchingCache
.createPropertyGetExpression(
typedMatchedExpression,
field.fieldName.text,
expression,
staticTarget: staticTarget,
typeArguments: typeArguments,
fileOffset: field.fileOffset,
);
DelayedExpression subExpression = visitPattern(
field.pattern,
objectExpression,
);
if (!subExpression.uses(objectExpression)) {
// Ensure that we perform the access even if we don't use the result.
matchingExpression = DelayedAndExpression.merge(
matchingExpression,
new EffectExpression(objectExpression, subExpression),
fileOffset: node.fileOffset,
);
} else {
matchingExpression = DelayedAndExpression.merge(
matchingExpression,
subExpression,
fileOffset: node.fileOffset,
);
}
}
return matchingExpression ??
new BooleanExpression(true, fileOffset: node.fileOffset);
}
@override
DelayedExpression visitOrPattern(
OrPattern node,
CacheableExpression matchedExpression,
) {
matchingCache.declareJointVariables(
node.orPatternJointVariables,
node.left.declaredVariables,
node.right.declaredVariables,
);
return new DelayedOrExpression(
visitPattern(node.left, matchedExpression),
visitPattern(node.right, matchedExpression),
fileOffset: node.fileOffset,
);
}
@override
DelayedExpression visitRecordPattern(
RecordPattern node,
CacheableExpression matchedExpression,
) {
matchedExpression = matchedExpression.promote(node.matchedValueType!);
DelayedExpression? matchingExpression;
CacheableExpression typedMatchedExpression;
if (node.needsCheck) {
matchingExpression = matchingCache.createIsExpression(
matchedExpression,
node.requiredType!,
fileOffset: node.fileOffset,
);
typedMatchedExpression = new PromotedCacheableExpression(
matchedExpression,
node.requiredType!,
);
} else {
typedMatchedExpression = matchedExpression;
}
int recordFieldIndex = 0;
for (Pattern fieldPattern in node.patterns) {
CacheableExpression fieldExpression;
if (fieldPattern is NamedPattern) {
fieldExpression = matchingCache.createPropertyGetExpression(
typedMatchedExpression,
fieldPattern.name,
new DelayedRecordNameGet(
typedMatchedExpression,
node.lookupType!,
fieldPattern.name,
fileOffset: fieldPattern.fileOffset,
),
fileOffset: fieldPattern.fileOffset,
);
// [type] is computed by the CFE, so the absence of the named field is
// an internal error, and we check the condition with an assert rather
// than reporting a compile-time error.
assert(
node.requiredType!.named.any(
(named) => named.name == fieldPattern.name,
),
);
} else {
final int fieldIndex = recordFieldIndex;
fieldExpression = matchingCache.createPropertyGetExpression(
typedMatchedExpression,
'\$${fieldIndex + 1}',
new DelayedRecordIndexGet(
typedMatchedExpression,
node.lookupType!,
fieldIndex,
fileOffset: fieldPattern.fileOffset,
),
fileOffset: fieldPattern.fileOffset,
);
assert(recordFieldIndex < node.requiredType!.positional.length);
recordFieldIndex++;
}
DelayedExpression fieldMatcher = visitPattern(
fieldPattern,
fieldExpression,
);
matchingExpression = DelayedAndExpression.merge(
matchingExpression,
fieldMatcher,
fileOffset: node.fileOffset,
);
}
// Coverage-ignore(suite): Not run.
return matchingExpression ??
new BooleanExpression(true, fileOffset: node.fileOffset);
}
@override
DelayedExpression visitRelationalPattern(
RelationalPattern node,
CacheableExpression matchedExpression,
) {
matchedExpression = matchedExpression.promote(node.matchedValueType!);
CacheableExpression constant = matchingCache.createConstantExpression(
node.expressionValue!,
node.expressionType!,
fileOffset: node.expression.fileOffset,
);
switch (node.kind) {
case RelationalPatternKind.equals:
case RelationalPatternKind.notEquals:
DelayedExpression expression = matchingCache.createEqualsExpression(
matchedExpression,
constant,
new DelayedEqualsExpression(
matchedExpression,
constant,
node.target!,
node.functionType!,
fileOffset: node.fileOffset,
),
fileOffset: node.fileOffset,
);
if (node.kind == RelationalPatternKind.notEquals) {
expression = new DelayedNotExpression(expression);
}
return expression;
case RelationalPatternKind.lessThan:
case RelationalPatternKind.lessThanEqual:
case RelationalPatternKind.greaterThan:
case RelationalPatternKind.greaterThanEqual:
DelayedExpression expression;
Member? staticTarget;
List<DartType>? typeArguments;
switch (node.accessKind) {
case RelationalAccessKind.Instance:
FunctionType functionType = node.functionType!;
DartType argumentType = functionType.positionalParameters.single;
expression = new DelayedInstanceInvocation(
matchedExpression,
node.target!,
functionType,
[
new DelayedAsExpression(
constant,
argumentType,
isImplicit: true,
fileOffset: node.fileOffset,
),
],
fileOffset: node.fileOffset,
);
break;
case RelationalAccessKind.Static:
FunctionType functionType = node.functionType!;
DartType argumentType = functionType.positionalParameters.single;
expression = new DelayedExtensionInvocation(
node.target!,
[
matchedExpression,
new DelayedAsExpression(
constant,
argumentType,
isImplicit: true,
fileOffset: node.fileOffset,
),
],
node.typeArguments!,
functionType.returnType,
fileOffset: node.fileOffset,
);
staticTarget = node.target;
typeArguments = node.typeArguments!;
break;
case RelationalAccessKind.Dynamic:
expression = new DelayedDynamicInvocation(
matchedExpression,
node.name!,
[constant],
DynamicAccessKind.Dynamic,
const DynamicType(),
fileOffset: node.fileOffset,
);
break;
case RelationalAccessKind.Never:
expression = new DelayedDynamicInvocation(
matchedExpression,
node.name!,
[constant],
DynamicAccessKind.Never,
const NeverType.nonNullable(),
fileOffset: node.fileOffset,
);
break;
case RelationalAccessKind.Invalid:
expression = new DelayedDynamicInvocation(
matchedExpression,
node.name!,
[constant],
DynamicAccessKind.Invalid,
const InvalidType(),
fileOffset: node.fileOffset,
);
break;
}
expression = new DelayedAsExpression(
expression,
coreTypes.boolNonNullableRawType,
isImplicit: true,
fileOffset: node.fileOffset,
);
return matchingCache.createComparisonExpression(
matchedExpression,
node.name!.text,
constant,
expression,
staticTarget: staticTarget,
typeArguments: typeArguments,
fileOffset: node.fileOffset,
);
}
}
@override
DelayedExpression visitRestPattern(
RestPattern node,
CacheableExpression matchedExpression,
) {
if (node.subPattern != null) {
return visitPattern(node.subPattern!, matchedExpression);
}
// Coverage-ignore-block(suite): Not run.
throw new UnsupportedError("RestPattern without subpattern.");
}
@override
DelayedExpression visitVariablePattern(
VariablePattern node,
CacheableExpression matchedExpression,
) {
matchedExpression = matchedExpression.promote(node.matchedValueType!);
DelayedExpression? matchingExpression;
if (node.type != null) {
matchingExpression = new DelayedIsExpression(
matchedExpression,
node.type!,
fileOffset: node.fileOffset,
);
}
Variable target = matchingCache.getUnaliasedVariable(node.variable);
// TODO(johnniwinther): Avoid hoisting variables for irrefutable tails.
target.isHoisted = true;
CacheableExpression valueExpression = new PromotedCacheableExpression(
matchedExpression,
target.type,
);
return DelayedAndExpression.merge(
matchingExpression,
new DelayedAssignment(
matchingCache,
target,
target.type,
valueExpression,
hasEffect: false,
fileOffset: node.fileOffset,
),
fileOffset: node.fileOffset,
);
}
@override
DelayedExpression visitWildcardPattern(
WildcardPattern node,
CacheableExpression matchedExpression,
) {
if (node.type != null) {
return new DelayedIsExpression(
matchedExpression,
node.type!,
fileOffset: node.fileOffset,
);
} else {
return new BooleanExpression(true, fileOffset: node.fileOffset);
}
}
@override
DelayedExpression visitAuxiliaryPattern(
AuxiliaryPattern node,
CacheableExpression matchedExpression,
) {
throw new UnsupportedError(
"Unexpected pattern $node (${node.runtimeType}).",
);
}
}