blob: 0833c83d554ff060e8f4d2dbb6be2aab6e914075 [file] [log] [blame]
// Copyright (c) 2017, 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' as ir;
import 'package:kernel/class_hierarchy.dart' as ir;
import 'package:kernel/core_types.dart' as ir;
import 'package:kernel/type_algebra.dart' as ir;
import 'package:kernel/type_environment.dart' as ir;
/// Visitor that computes the static type of an expression.
///
/// This visitor doesn't traverse subtrees that are not needed for computing
/// the static type.
// TODO(johnniwinther): Add improved type promotion to handle negative
// reasoning.
abstract class StaticTypeVisitor extends ir.Visitor<ir.DartType> {
ir.TypeEnvironment _typeEnvironment;
StaticTypeVisitor(this._typeEnvironment);
fail(String message) => message;
ir.TypeEnvironment get typeEnvironment => _typeEnvironment;
/// Returns the static type of the expression as an instantiation of
/// [superclass].
///
/// Should only be used on code compiled in strong mode, as this method
/// assumes the IR is strongly typed.
///
/// This method furthermore assumes that the type of the expression actually
/// is a subtype of (some instantiation of) the given [superclass].
/// If this is not the case the raw type of [superclass] is returned.
///
/// This method is derived from `ir.Expression.getStaticTypeAsInstanceOf`.
ir.InterfaceType getTypeAsInstanceOf(ir.DartType type, ir.Class superclass) {
// This method assumes the program is correctly typed, so if the superclass
// is not generic, we can just return its raw type without computing the
// type of this expression. It also ensures that all types are considered
// subtypes of Object (not just interface types), and function types are
// considered subtypes of Function.
if (superclass.typeParameters.isEmpty) {
return superclass.rawType;
}
while (type is ir.TypeParameterType) {
type = (type as ir.TypeParameterType).parameter.bound;
}
if (type is ir.InterfaceType) {
ir.InterfaceType upcastType =
typeEnvironment.hierarchy.getTypeAsInstanceOf(type, superclass);
if (upcastType != null) return upcastType;
} else if (type is ir.BottomType) {
return superclass.bottomType;
}
return superclass.rawType;
}
@override
ir.DartType defaultNode(ir.Node node) {
return null;
}
ir.DartType visitNode(ir.Node node) {
return node?.accept(this);
}
Null visitNodes(List<ir.Node> nodes) {
for (ir.Node node in nodes) {
visitNode(node);
}
}
ir.DartType defaultExpression(ir.Expression node) {
throw fail('Unhandled node $node (${node.runtimeType})');
}
@override
ir.DartType visitAsExpression(ir.AsExpression node) {
return node.type;
}
@override
ir.DartType visitAwaitExpression(ir.AwaitExpression node) {
return typeEnvironment.unfutureType(visitNode(node.operand));
}
@override
ir.DartType visitBoolLiteral(ir.BoolLiteral node) => typeEnvironment.boolType;
@override
ir.DartType visitCheckLibraryIsLoaded(ir.CheckLibraryIsLoaded node) =>
typeEnvironment.objectType;
@override
ir.DartType visitStringLiteral(ir.StringLiteral node) =>
typeEnvironment.stringType;
@override
ir.DartType visitStringConcatenation(ir.StringConcatenation node) {
return typeEnvironment.stringType;
}
@override
ir.DartType visitNullLiteral(ir.NullLiteral node) => const ir.BottomType();
@override
ir.DartType visitIntLiteral(ir.IntLiteral node) => typeEnvironment.intType;
@override
ir.DartType visitDoubleLiteral(ir.DoubleLiteral node) =>
typeEnvironment.doubleType;
@override
ir.DartType visitSymbolLiteral(ir.SymbolLiteral node) =>
typeEnvironment.symbolType;
@override
ir.DartType visitListLiteral(ir.ListLiteral node) {
return typeEnvironment.literalListType(node.typeArgument);
}
@override
ir.DartType visitMapLiteral(ir.MapLiteral node) {
return typeEnvironment.literalMapType(node.keyType, node.valueType);
}
@override
ir.DartType visitVariableGet(ir.VariableGet node) =>
node.promotedType ?? node.variable.type;
@override
ir.DartType visitVariableSet(ir.VariableSet node) {
return visitNode(node.value);
}
ir.DartType computePropertyGetType(
ir.PropertyGet node, ir.DartType receiverType) {
ir.Member interfaceTarget = node.interfaceTarget;
if (interfaceTarget != null) {
ir.Class superclass = interfaceTarget.enclosingClass;
receiverType = getTypeAsInstanceOf(receiverType, superclass);
return ir.Substitution.fromInterfaceType(receiverType)
.substituteType(interfaceTarget.getterType);
}
// Treat the properties of Object specially.
String nameString = node.name.name;
if (nameString == 'hashCode') {
return typeEnvironment.intType;
} else if (nameString == 'runtimeType') {
return typeEnvironment.typeType;
}
return const ir.DynamicType();
}
@override
ir.DartType visitPropertyGet(ir.PropertyGet node) {
ir.DartType receiverType = visitNode(node.receiver);
return computePropertyGetType(node, receiverType);
}
@override
ir.DartType visitPropertySet(ir.PropertySet node) {
return visitNode(node.value);
}
@override
ir.DartType visitDirectPropertyGet(ir.DirectPropertyGet node) {
ir.DartType receiverType = visitNode(node.receiver);
ir.Class superclass = node.target.enclosingClass;
receiverType = getTypeAsInstanceOf(receiverType, superclass);
return ir.Substitution.fromInterfaceType(receiverType)
.substituteType(node.target.getterType);
}
@override
ir.DartType visitDirectMethodInvocation(ir.DirectMethodInvocation node) {
ir.DartType receiverType = visitNode(node.receiver);
if (typeEnvironment.isOverloadedArithmeticOperator(node.target)) {
ir.DartType argumentType = visitNode(node.arguments.positional[0]);
return typeEnvironment.getTypeOfOverloadedArithmetic(
receiverType, argumentType);
}
ir.Class superclass = node.target.enclosingClass;
receiverType = getTypeAsInstanceOf(receiverType, superclass);
ir.DartType returnType = ir.Substitution.fromInterfaceType(receiverType)
.substituteType(node.target.function.returnType);
return ir.Substitution.fromPairs(
node.target.function.typeParameters, node.arguments.types)
.substituteType(returnType);
}
@override
ir.DartType visitDirectPropertySet(ir.DirectPropertySet node) {
return visitNode(node.value);
}
@override
ir.DartType visitThisExpression(ir.ThisExpression node) =>
typeEnvironment.thisType;
/// Returns `true` if [interfaceTarget] is an arithmetic operator whose result
/// type is computed using both the receiver type and the argument type.
///
/// Visitors that subclass the [StaticTypeVisitor] must special case this
/// target as to avoid visiting the argument twice.
bool isSpecialCasedBinaryOperator(ir.Member interfaceTarget) {
return interfaceTarget is ir.Procedure &&
typeEnvironment.isOverloadedArithmeticOperator(interfaceTarget);
}
ir.DartType computeMethodInvocationType(
ir.MethodInvocation node, ir.DartType receiverType) {
ir.Member interfaceTarget = node.interfaceTarget;
if (interfaceTarget != null) {
if (isSpecialCasedBinaryOperator(interfaceTarget)) {
ir.DartType argumentType = visitNode(node.arguments.positional[0]);
return typeEnvironment.getTypeOfOverloadedArithmetic(
receiverType, argumentType);
}
ir.Class superclass = interfaceTarget.enclosingClass;
receiverType = getTypeAsInstanceOf(receiverType, superclass);
ir.DartType getterType = ir.Substitution.fromInterfaceType(receiverType)
.substituteType(interfaceTarget.getterType);
if (getterType is ir.FunctionType) {
return ir.Substitution.fromPairs(
getterType.typeParameters, node.arguments.types)
.substituteType(getterType.returnType);
} else {
return const ir.DynamicType();
}
}
if (node.name.name == 'call') {
if (receiverType is ir.FunctionType) {
if (receiverType.typeParameters.length != node.arguments.types.length) {
return const ir.BottomType();
}
return ir.Substitution.fromPairs(
receiverType.typeParameters, node.arguments.types)
.substituteType(receiverType.returnType);
}
}
if (node.name.name == '==') {
// We use this special case to simplify generation of '==' checks.
return typeEnvironment.boolType;
}
return const ir.DynamicType();
}
@override
ir.DartType visitMethodInvocation(ir.MethodInvocation node) {
ir.DartType receiverType = visitNode(node.receiver);
return computeMethodInvocationType(node, receiverType);
}
@override
ir.DartType visitStaticGet(ir.StaticGet node) => node.target.getterType;
@override
ir.DartType visitStaticSet(ir.StaticSet node) {
return visitNode(node.value);
}
@override
ir.DartType visitStaticInvocation(ir.StaticInvocation node) {
return ir.Substitution.fromPairs(
node.target.function.typeParameters, node.arguments.types)
.substituteType(node.target.function.returnType);
}
@override
ir.DartType visitConstructorInvocation(ir.ConstructorInvocation node) {
return node.arguments.types.isEmpty
? node.target.enclosingClass.rawType
: new ir.InterfaceType(
node.target.enclosingClass, node.arguments.types);
}
@override
ir.DartType visitSuperPropertyGet(ir.SuperPropertyGet node) {
if (node.interfaceTarget == null) {
// TODO(johnniwinther): Resolve and set the target here.
return const ir.DynamicType();
}
ir.Class declaringClass = node.interfaceTarget.enclosingClass;
if (declaringClass.typeParameters.isEmpty) {
return node.interfaceTarget.getterType;
}
ir.DartType receiver = typeEnvironment.hierarchy
.getTypeAsInstanceOf(typeEnvironment.thisType, declaringClass);
return ir.Substitution.fromInterfaceType(receiver)
.substituteType(node.interfaceTarget.getterType);
}
@override
ir.DartType visitSuperPropertySet(ir.SuperPropertySet node) {
return visitNode(node.value);
}
@override
ir.DartType visitSuperMethodInvocation(ir.SuperMethodInvocation node) {
if (node.interfaceTarget == null) {
// TODO(johnniwinther): Resolve and set the target here.
return const ir.DynamicType();
}
ir.Class superclass = node.interfaceTarget.enclosingClass;
ir.InterfaceType receiverType = typeEnvironment.hierarchy
.getTypeAsInstanceOf(typeEnvironment.thisType, superclass);
ir.DartType returnType = ir.Substitution.fromInterfaceType(receiverType)
.substituteType(node.interfaceTarget.function.returnType);
return ir.Substitution.fromPairs(
node.interfaceTarget.function.typeParameters, node.arguments.types)
.substituteType(returnType);
}
@override
ir.DartType visitThrow(ir.Throw node) => const ir.BottomType();
@override
ir.DartType visitRethrow(ir.Rethrow node) => const ir.BottomType();
@override
ir.DartType visitLogicalExpression(ir.LogicalExpression node) =>
typeEnvironment.boolType;
@override
ir.DartType visitNot(ir.Not node) {
return typeEnvironment.boolType;
}
@override
ir.DartType visitConditionalExpression(ir.ConditionalExpression node) {
return node.staticType;
}
@override
ir.DartType visitIsExpression(ir.IsExpression node) {
return typeEnvironment.boolType;
}
@override
ir.DartType visitTypeLiteral(ir.TypeLiteral node) => typeEnvironment.typeType;
@override
ir.DartType visitFunctionExpression(ir.FunctionExpression node) {
return node.function.functionType;
}
@override
ir.DartType visitLet(ir.Let node) {
return visitNode(node.body);
}
ir.DartType computeInstantiationType(
ir.Instantiation node, ir.FunctionType expressionType) {
return ir.Substitution.fromPairs(
expressionType.typeParameters, node.typeArguments)
.substituteType(expressionType.withoutTypeParameters);
}
@override
ir.DartType visitInstantiation(ir.Instantiation node) {
ir.FunctionType expressionType = visitNode(node.expression);
return computeInstantiationType(node, expressionType);
}
@override
ir.DartType visitInvalidExpression(ir.InvalidExpression node) =>
const ir.BottomType();
@override
ir.DartType visitLoadLibrary(ir.LoadLibrary node) {
return typeEnvironment.futureType(const ir.DynamicType());
}
}
/// Visitor that computes the static type of an expression using a cache to
/// avoid recomputations.
class CachingStaticTypeVisitor extends StaticTypeVisitor {
Map<ir.Expression, ir.DartType> _cache = {};
CachingStaticTypeVisitor(ir.TypeEnvironment typeEnvironment)
: super(typeEnvironment);
@override
ir.DartType visitNode(ir.Node node) {
ir.DartType result;
if (node is ir.Expression) {
result = _cache[node];
if (result != null) return result;
result = super.visitNode(node);
_cache[node] = result;
} else {
result = super.visitNode(node);
}
return result;
}
}
/// Visitor that traverse the whole tree while returning the static type of
/// expressions.
abstract class StaticTypeTraversalVisitor extends StaticTypeVisitor {
StaticTypeTraversalVisitor(ir.TypeEnvironment typeEnvironment)
: super(typeEnvironment);
@override
ir.DartType defaultNode(ir.Node node) {
node.visitChildren(this);
return null;
}
Null defaultMember(ir.Member node) {
typeEnvironment.thisType =
node.enclosingClass != null ? node.enclosingClass.thisType : null;
node.visitChildren(this);
typeEnvironment.thisType = null;
return null;
}
ir.DartType visitExpressionStatement(ir.ExpressionStatement node) {
visitNode(node.expression);
return null;
}
@override
ir.DartType visitAsExpression(ir.AsExpression node) {
visitNode(node.operand);
return super.visitAsExpression(node);
}
@override
ir.DartType visitStringConcatenation(ir.StringConcatenation node) {
visitNodes(node.expressions);
return super.visitStringConcatenation(node);
}
@override
ir.DartType visitListLiteral(ir.ListLiteral node) {
visitNodes(node.expressions);
return super.visitListLiteral(node);
}
@override
ir.DartType visitMapLiteral(ir.MapLiteral node) {
visitNodes(node.entries);
return super.visitMapLiteral(node);
}
@override
ir.DartType visitPropertySet(ir.PropertySet node) {
visitNode(node.receiver);
return super.visitPropertySet(node);
}
@override
ir.DartType visitDirectMethodInvocation(ir.DirectMethodInvocation node) {
visitNodes(node.arguments.positional);
visitNodes(node.arguments.named);
return super.visitDirectMethodInvocation(node);
}
@override
ir.DartType visitDirectPropertySet(ir.DirectPropertySet node) {
visitNode(node.receiver);
return super.visitDirectPropertySet(node);
}
@override
ir.DartType visitMethodInvocation(ir.MethodInvocation node) {
if (isSpecialCasedBinaryOperator(node.interfaceTarget)) {
return super.visitMethodInvocation(node);
}
visitNodes(node.arguments.positional);
visitNodes(node.arguments.named);
return super.visitMethodInvocation(node);
}
@override
ir.DartType visitStaticInvocation(ir.StaticInvocation node) {
visitNodes(node.arguments.positional);
visitNodes(node.arguments.named);
return super.visitStaticInvocation(node);
}
@override
ir.DartType visitConstructorInvocation(ir.ConstructorInvocation node) {
visitNodes(node.arguments.positional);
visitNodes(node.arguments.named);
return super.visitConstructorInvocation(node);
}
@override
ir.DartType visitSuperMethodInvocation(ir.SuperMethodInvocation node) {
visitNodes(node.arguments.positional);
visitNodes(node.arguments.named);
return super.visitSuperMethodInvocation(node);
}
@override
ir.DartType visitThrow(ir.Throw node) {
visitNode(node.expression);
return super.visitThrow(node);
}
@override
ir.DartType visitLogicalExpression(ir.LogicalExpression node) {
visitNode(node.left);
visitNode(node.right);
return super.visitLogicalExpression(node);
}
@override
ir.DartType visitNot(ir.Not node) {
visitNode(node.operand);
return super.visitNot(node);
}
@override
ir.DartType visitConditionalExpression(ir.ConditionalExpression node) {
visitNode(node.condition);
visitNode(node.then);
visitNode(node.otherwise);
return super.visitConditionalExpression(node);
}
@override
ir.DartType visitIsExpression(ir.IsExpression node) {
visitNode(node.operand);
return super.visitIsExpression(node);
}
@override
ir.DartType visitFunctionExpression(ir.FunctionExpression node) {
visitNode(node.function.body);
return super.visitFunctionExpression(node);
}
@override
ir.DartType visitLet(ir.Let node) {
visitNode(node.variable.initializer);
return super.visitLet(node);
}
}