blob: 83229541b126bf43a58e1f903a2c6ba03c0f155a [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.
import 'package:kernel/ast.dart' hide MapEntry;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/visitor.dart' show ExpressionVisitor, DartTypeVisitor;
import '../names.dart'
show
ampersandName,
barName,
caretName,
divisionName,
equalsName,
greaterThanName,
greaterThanOrEqualsName,
identicalName,
leftShiftName,
lengthName,
lessThanName,
lessThanOrEqualsName,
minusName,
multiplyName,
mustacheName,
negationName,
percentName,
plusName,
rightShiftName,
tildeName,
unaryMinusName;
import '../fasta_codes.dart' show templateInternalVisitorUnsupportedDefault;
import '../problems.dart' show unsupported;
/// Evaluates constness of the given constructor invocation.
///
/// TODO(dmitryas): Share code with the constant evaluator from
/// pkg/kernel/lib/transformations/constants.dart.
class ConstnessEvaluator
implements ExpressionVisitor<bool>, DartTypeVisitor<bool> {
final CoreTypes coreTypes;
/// [Uri] of the file containing the expressions that are to be evaluated.
final Uri uri;
ConstnessEvaluator(this.coreTypes, this.uri);
@override
defaultExpression(Expression node) {
return unsupported(
templateInternalVisitorUnsupportedDefault
.withArguments("${node.runtimeType}")
.message,
node?.fileOffset ?? -1,
uri);
}
@override
defaultBasicLiteral(BasicLiteral node) {
return defaultExpression(node);
}
@override
defaultDartType(DartType node) {
return unsupported(
templateInternalVisitorUnsupportedDefault
.withArguments("${node.runtimeType}")
.message,
-1,
uri);
}
@override
visitInvalidType(InvalidType node) => false;
@override
visitDynamicType(DynamicType node) => true;
@override
visitVoidType(VoidType node) => true;
@override
visitBottomType(BottomType node) => true;
@override
visitInterfaceType(InterfaceType node) {
for (var type in node.typeArguments) {
if (!type.accept(this)) return false;
}
return true;
}
@override
visitVectorType(VectorType node) => true;
@override
visitFunctionType(FunctionType node) {
if (!node.returnType.accept(this)) return false;
for (var parameter in node.positionalParameters) {
if (!parameter.accept(this)) return false;
}
for (var parameter in node.namedParameters) {
if (!parameter.type.accept(this)) return false;
}
for (var typeParameter in node.typeParameters) {
if (!typeParameter.bound.accept(this)) return false;
}
return true;
}
@override
visitTypeParameterType(TypeParameterType node) => false;
@override
visitTypedefType(TypedefType node) {
for (var type in node.typeArguments) {
if (!type.accept(this)) return false;
}
return true;
}
bool evaluate(Expression node) {
return node.accept(this);
}
@override
visitNullLiteral(NullLiteral node) {
return true;
}
@override
visitBoolLiteral(BoolLiteral node) {
return true;
}
@override
visitIntLiteral(IntLiteral node) {
return true;
}
@override
visitDoubleLiteral(DoubleLiteral node) {
return true;
}
@override
visitStringLiteral(StringLiteral node) {
return true;
}
@override
visitSymbolLiteral(SymbolLiteral node) {
return true;
}
@override
visitTypeLiteral(TypeLiteral node) {
// TODO(dmitryas): Figure out how to handle deferred types.
return node.type.accept(this);
}
@override
visitListLiteral(ListLiteral node) {
return node.isConst;
}
@override
visitMapLiteral(MapLiteral node) {
return node.isConst;
}
@override
visitConstructorInvocation(ConstructorInvocation node) {
if (!node.target.isConst) {
return false;
}
for (var type in node.arguments.types) {
if (!type.accept(this)) return false;
}
for (var expression in node.arguments.positional) {
if (!expression.accept(this)) return false;
}
for (var namedExpression in node.arguments.named) {
if (!namedExpression.value.accept(this)) return false;
}
return true;
}
@override
visitMethodInvocation(MethodInvocation node) {
if (node.arguments.named.isNotEmpty ||
node.arguments.types.isNotEmpty ||
!isConstantMethodName(node.name, node.arguments.positional.length)) {
return false;
}
if (!node.receiver.accept(this)) return false;
for (var expression in node.arguments.positional) {
if (!expression.accept(this)) return false;
}
return true;
}
@override
visitLogicalExpression(LogicalExpression node) {
return node.left.accept(this) && node.right.accept(this);
}
@override
visitConditionalExpression(ConditionalExpression node) {
return node.condition.accept(this) &&
node.then.accept(this) &&
node.otherwise.accept(this);
}
@override
visitPropertyGet(PropertyGet node) {
if (!isConstantPropertyName(node.name)) return false;
return node.receiver.accept(this);
}
@override
visitLet(Let node) {
return node.variable.initializer.accept(this) && node.body.accept(this);
}
@override
visitVariableGet(VariableGet node) {
return node.variable.isConst;
}
@override
visitStaticGet(StaticGet node) {
Member target = node.target;
if (target is Field) {
return target.isConst;
} else {
// TODO(dmitryas): Figure out how to deal with deferred functions.
return true;
}
}
@override
visitStringConcatenation(StringConcatenation node) {
for (var expression in node.expressions) {
if (!expression.accept(this)) return false;
}
return true;
}
@override
visitStaticInvocation(StaticInvocation node) {
Member target = node.target;
if (target is Procedure) {
if (target.isConst && target.isFactory) {
for (var type in node.arguments.types) {
if (!type.accept(this)) return false;
}
for (var expression in node.arguments.positional) {
if (!expression.accept(this)) return false;
}
for (var namedExpression in node.arguments.named) {
if (!namedExpression.value.accept(this)) return false;
}
return true;
} else if (target.name == identicalName) {
final TreeNode parent = target.parent;
if (parent is Library && parent == coreTypes.coreLibrary) {
assert(node.arguments.positional.length == 2);
return node.arguments.positional[0].accept(this) &&
node.arguments.positional[1].accept(this);
}
}
}
return false;
}
@override
visitAsExpression(AsExpression node) {
return node.operand.accept(this) && node.type.accept(this);
}
@override
visitNot(Not node) {
return node.operand.accept(this);
}
@override
visitInvalidExpression(InvalidExpression node) => false;
@override
visitVariableSet(VariableSet node) => false;
@override
visitPropertySet(PropertySet node) => false;
@override
visitDirectPropertyGet(DirectPropertyGet node) {
if (!isConstantPropertyName(node.target.name)) return false;
return node.receiver.accept(this);
}
@override
visitDirectPropertySet(DirectPropertySet node) => false;
@override
visitSuperPropertyGet(SuperPropertyGet node) => false;
@override
visitSuperPropertySet(SuperPropertySet node) => false;
@override
visitStaticSet(StaticSet node) => false;
@override
visitDirectMethodInvocation(DirectMethodInvocation node) {
if (node.arguments.named.isNotEmpty ||
node.arguments.types.isNotEmpty ||
!isConstantMethodName(node.name, node.arguments.positional.length)) {
return false;
}
if (!node.receiver.accept(this)) return false;
for (var expression in node.arguments.positional) {
if (!expression.accept(this)) return false;
}
return true;
}
@override
visitSuperMethodInvocation(SuperMethodInvocation node) => false;
@override
visitIsExpression(IsExpression node) {
return node.operand.accept(this) && node.type.accept(this);
}
@override
visitThisExpression(ThisExpression node) => false;
@override
visitRethrow(Rethrow node) => false;
@override
visitThrow(Throw node) => false;
@override
visitAwaitExpression(AwaitExpression node) => false;
@override
visitFunctionExpression(FunctionExpression node) => false;
@override
visitConstantExpression(ConstantExpression node) => true;
@override
visitInstantiation(Instantiation node) => false;
@override
visitLoadLibrary(LoadLibrary node) => false;
@override
visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) => false;
@override
visitVectorCreation(VectorCreation node) => false;
@override
visitVectorGet(VectorGet node) => false;
@override
visitVectorSet(VectorSet node) => false;
@override
visitVectorCopy(VectorCopy node) => false;
@override
visitClosureCreation(ClosureCreation node) => false;
/// Returns null if `receiver.name(arguments)` is not a constant.
static bool isConstantMethodName(Name name, int argumentCount) {
if (argumentCount == 0) {
if (name == tildeName) return true;
if (name == unaryMinusName) return true;
} else if (argumentCount == 1) {
if (name == ampersandName) return true;
if (name == barName) return true;
if (name == caretName) return true;
if (name == divisionName) return true;
if (name == equalsName) return true;
if (name == greaterThanName) return true;
if (name == greaterThanOrEqualsName) return true;
if (name == leftShiftName) return true;
if (name == lessThanName) return true;
if (name == lessThanOrEqualsName) return true;
if (name == minusName) return true;
if (name == multiplyName) return true;
if (name == mustacheName) return true;
if (name == negationName) return true;
if (name == percentName) return true;
if (name == plusName) return true;
if (name == rightShiftName) return true;
}
return false;
}
static bool isConstantPropertyName(Name name) {
return name == lengthName;
}
}
// TODO(32717): Remove this helper function when the issue is resolved.
bool evaluateConstness(Expression expression, CoreTypes coreTypes, Uri uri) {
return new ConstnessEvaluator(coreTypes, uri).evaluate(expression);
}