mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 04:37:12 +00:00
53321309da
They were supposed to be used as a part of the closure conversion pass, which is now obsoleted. Change-Id: Ie063f6c44487df7cd5d21895e8edc03251525d5f Reviewed-on: https://dart-review.googlesource.com/68662 Commit-Queue: Dmitry Stefantsov <dmitryas@google.com> Reviewed-by: Jens Johansen <jensj@google.com>
993 lines
31 KiB
Dart
993 lines
31 KiB
Dart
// Copyright (c) 2016, 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.
|
|
library kernel.type_checker;
|
|
|
|
import 'ast.dart';
|
|
import 'class_hierarchy.dart';
|
|
import 'core_types.dart';
|
|
import 'type_algebra.dart';
|
|
import 'type_environment.dart';
|
|
|
|
/// Performs strong-mode type checking on the kernel IR.
|
|
///
|
|
/// A concrete subclass of [TypeChecker] must implement [checkAssignable] and
|
|
/// [fail] in order to deal with subtyping requirements and error handling.
|
|
abstract class TypeChecker {
|
|
final CoreTypes coreTypes;
|
|
final ClassHierarchy hierarchy;
|
|
final bool ignoreSdk;
|
|
TypeEnvironment environment;
|
|
|
|
TypeChecker(this.coreTypes, this.hierarchy,
|
|
{bool strongMode: false, this.ignoreSdk: true}) {
|
|
environment =
|
|
new TypeEnvironment(coreTypes, hierarchy, strongMode: strongMode);
|
|
}
|
|
|
|
void checkComponent(Component component) {
|
|
for (var library in component.libraries) {
|
|
if (ignoreSdk && library.importUri.scheme == 'dart') continue;
|
|
for (var class_ in library.classes) {
|
|
hierarchy.forEachOverridePair(class_,
|
|
(Member ownMember, Member superMember, bool isSetter) {
|
|
checkOverride(class_, ownMember, superMember, isSetter);
|
|
});
|
|
}
|
|
}
|
|
var visitor = new TypeCheckingVisitor(this, environment);
|
|
for (var library in component.libraries) {
|
|
if (ignoreSdk && library.importUri.scheme == 'dart') continue;
|
|
for (var class_ in library.classes) {
|
|
environment.thisType = class_.thisType;
|
|
for (var field in class_.fields) {
|
|
visitor.visitField(field);
|
|
}
|
|
for (var constructor in class_.constructors) {
|
|
visitor.visitConstructor(constructor);
|
|
}
|
|
for (var procedure in class_.procedures) {
|
|
visitor.visitProcedure(procedure);
|
|
}
|
|
}
|
|
environment.thisType = null;
|
|
for (var procedure in library.procedures) {
|
|
visitor.visitProcedure(procedure);
|
|
}
|
|
for (var field in library.fields) {
|
|
visitor.visitField(field);
|
|
}
|
|
}
|
|
}
|
|
|
|
DartType getterType(Class host, Member member) {
|
|
var hostType = hierarchy.getClassAsInstanceOf(host, member.enclosingClass);
|
|
var substitution = Substitution.fromSupertype(hostType);
|
|
return substitution.substituteType(member.getterType);
|
|
}
|
|
|
|
DartType setterType(Class host, Member member) {
|
|
var hostType = hierarchy.getClassAsInstanceOf(host, member.enclosingClass);
|
|
var substitution = Substitution.fromSupertype(hostType);
|
|
return substitution.substituteType(member.setterType, contravariant: true);
|
|
}
|
|
|
|
/// Check that [ownMember] of [host] can override [superMember].
|
|
void checkOverride(
|
|
Class host, Member ownMember, Member superMember, bool isSetter) {
|
|
if (isSetter) {
|
|
checkAssignable(ownMember, setterType(host, superMember),
|
|
setterType(host, ownMember));
|
|
} else {
|
|
checkAssignable(ownMember, getterType(host, ownMember),
|
|
getterType(host, superMember));
|
|
}
|
|
}
|
|
|
|
/// Check that [from] is a subtype of [to].
|
|
///
|
|
/// [where] is an AST node indicating roughly where the check is required.
|
|
void checkAssignable(TreeNode where, DartType from, DartType to);
|
|
|
|
/// Checks that [expression], which has type [from], can be assigned to [to].
|
|
///
|
|
/// Should return a downcast if necessary, or [expression] if no cast is
|
|
/// needed.
|
|
Expression checkAndDowncastExpression(
|
|
Expression expression, DartType from, DartType to) {
|
|
checkAssignable(expression, from, to);
|
|
return expression;
|
|
}
|
|
|
|
/// Check unresolved invocation (one that has no interfaceTarget)
|
|
/// and report an error if necessary.
|
|
void checkUnresolvedInvocation(DartType receiver, TreeNode where) {
|
|
// By default we ignore unresolved method invocations.
|
|
}
|
|
|
|
/// Indicates that type checking failed.
|
|
void fail(TreeNode where, String message);
|
|
}
|
|
|
|
class TypeCheckingVisitor
|
|
implements
|
|
ExpressionVisitor<DartType>,
|
|
StatementVisitor<Null>,
|
|
MemberVisitor<Null>,
|
|
InitializerVisitor<Null> {
|
|
final TypeChecker checker;
|
|
final TypeEnvironment environment;
|
|
|
|
CoreTypes get coreTypes => environment.coreTypes;
|
|
ClassHierarchy get hierarchy => environment.hierarchy;
|
|
Class get currentClass => environment.thisType.classNode;
|
|
|
|
TypeCheckingVisitor(this.checker, this.environment);
|
|
|
|
void checkAssignable(TreeNode where, DartType from, DartType to) {
|
|
checker.checkAssignable(where, from, to);
|
|
}
|
|
|
|
void checkUnresolvedInvocation(DartType receiver, TreeNode where) {
|
|
checker.checkUnresolvedInvocation(receiver, where);
|
|
}
|
|
|
|
Expression checkAndDowncastExpression(Expression from, DartType to) {
|
|
var parent = from.parent;
|
|
var type = visitExpression(from);
|
|
var result = checker.checkAndDowncastExpression(from, type, to);
|
|
result.parent = parent;
|
|
return result;
|
|
}
|
|
|
|
void checkExpressionNoDowncast(Expression expression, DartType to) {
|
|
checkAssignable(expression, visitExpression(expression), to);
|
|
}
|
|
|
|
void fail(TreeNode node, String message) {
|
|
checker.fail(node, message);
|
|
}
|
|
|
|
DartType visitExpression(Expression node) => node.accept(this);
|
|
|
|
void visitStatement(Statement node) {
|
|
node.accept(this);
|
|
}
|
|
|
|
void visitInitializer(Initializer node) {
|
|
node.accept(this);
|
|
}
|
|
|
|
defaultMember(Member node) => throw 'Unused';
|
|
|
|
DartType defaultBasicLiteral(BasicLiteral node) {
|
|
return defaultExpression(node);
|
|
}
|
|
|
|
DartType defaultExpression(Expression node) {
|
|
throw 'Unexpected expression ${node.runtimeType}';
|
|
}
|
|
|
|
defaultStatement(Statement node) {
|
|
throw 'Unexpected statement ${node.runtimeType}';
|
|
}
|
|
|
|
defaultInitializer(Initializer node) {
|
|
throw 'Unexpected initializer ${node.runtimeType}';
|
|
}
|
|
|
|
visitField(Field node) {
|
|
if (node.initializer != null) {
|
|
node.initializer =
|
|
checkAndDowncastExpression(node.initializer, node.type);
|
|
}
|
|
}
|
|
|
|
visitConstructor(Constructor node) {
|
|
environment.returnType = null;
|
|
environment.yieldType = null;
|
|
node.initializers.forEach(visitInitializer);
|
|
handleFunctionNode(node.function);
|
|
}
|
|
|
|
visitProcedure(Procedure node) {
|
|
environment.returnType = _getInternalReturnType(node.function);
|
|
environment.yieldType = _getYieldType(node.function);
|
|
handleFunctionNode(node.function);
|
|
}
|
|
|
|
visitRedirectingFactoryConstructor(RedirectingFactoryConstructor node) {
|
|
environment.returnType = null;
|
|
environment.yieldType = null;
|
|
}
|
|
|
|
void handleFunctionNode(FunctionNode node) {
|
|
var oldAsyncMarker = environment.currentAsyncMarker;
|
|
environment.currentAsyncMarker = node.asyncMarker;
|
|
node.positionalParameters
|
|
.skip(node.requiredParameterCount)
|
|
.forEach(handleOptionalParameter);
|
|
node.namedParameters.forEach(handleOptionalParameter);
|
|
if (node.body != null) {
|
|
visitStatement(node.body);
|
|
}
|
|
environment.currentAsyncMarker = oldAsyncMarker;
|
|
}
|
|
|
|
void handleNestedFunctionNode(FunctionNode node) {
|
|
var oldReturn = environment.returnType;
|
|
var oldYield = environment.yieldType;
|
|
environment.returnType = _getInternalReturnType(node);
|
|
environment.yieldType = _getYieldType(node);
|
|
handleFunctionNode(node);
|
|
environment.returnType = oldReturn;
|
|
environment.yieldType = oldYield;
|
|
}
|
|
|
|
void handleOptionalParameter(VariableDeclaration parameter) {
|
|
if (parameter.initializer != null) {
|
|
// Default parameter values cannot be downcast.
|
|
checkExpressionNoDowncast(parameter.initializer, parameter.type);
|
|
}
|
|
}
|
|
|
|
Substitution getReceiverType(
|
|
TreeNode access, Expression receiver, Member member) {
|
|
var type = visitExpression(receiver);
|
|
Class superclass = member.enclosingClass;
|
|
if (superclass.supertype == null) {
|
|
return Substitution.empty; // Members on Object are always accessible.
|
|
}
|
|
while (type is TypeParameterType) {
|
|
type = (type as TypeParameterType).bound;
|
|
}
|
|
if (type is BottomType) {
|
|
// The bottom type is a subtype of all types, so it should be allowed.
|
|
return Substitution.bottomForClass(superclass);
|
|
}
|
|
if (type is InterfaceType) {
|
|
// The receiver type should implement the interface declaring the member.
|
|
var upcastType = hierarchy.getTypeAsInstanceOf(type, superclass);
|
|
if (upcastType != null) {
|
|
return Substitution.fromInterfaceType(upcastType);
|
|
}
|
|
}
|
|
if (type is FunctionType && superclass == coreTypes.functionClass) {
|
|
assert(type.typeParameters.isEmpty);
|
|
return Substitution.empty;
|
|
}
|
|
// Note that we do not allow 'dynamic' here. Dynamic calls should not
|
|
// have a declared interface target.
|
|
fail(access, '$member is not accessible on a receiver of type $type');
|
|
return Substitution.bottomForClass(superclass); // Continue type checking.
|
|
}
|
|
|
|
Substitution getSuperReceiverType(Member member) {
|
|
return Substitution.fromSupertype(
|
|
hierarchy.getClassAsInstanceOf(currentClass, member.enclosingClass));
|
|
}
|
|
|
|
DartType handleCall(Arguments arguments, DartType functionType,
|
|
{Substitution receiver: Substitution.empty,
|
|
List<TypeParameter> typeParameters}) {
|
|
if (functionType is FunctionType) {
|
|
typeParameters ??= functionType.typeParameters;
|
|
if (arguments.positional.length < functionType.requiredParameterCount) {
|
|
fail(arguments, 'Too few positional arguments');
|
|
return const BottomType();
|
|
}
|
|
if (arguments.positional.length >
|
|
functionType.positionalParameters.length) {
|
|
fail(arguments, 'Too many positional arguments');
|
|
return const BottomType();
|
|
}
|
|
var typeArguments = arguments.types;
|
|
if (typeArguments.length != typeParameters.length) {
|
|
fail(arguments, 'Wrong number of type arguments');
|
|
return const BottomType();
|
|
}
|
|
Substitution substitution = _instantiateFunction(
|
|
typeParameters, typeArguments, arguments,
|
|
receiverSubstitution: receiver);
|
|
for (int i = 0; i < arguments.positional.length; ++i) {
|
|
var expectedType = substitution.substituteType(
|
|
functionType.positionalParameters[i],
|
|
contravariant: true);
|
|
arguments.positional[i] =
|
|
checkAndDowncastExpression(arguments.positional[i], expectedType);
|
|
}
|
|
for (int i = 0; i < arguments.named.length; ++i) {
|
|
var argument = arguments.named[i];
|
|
bool found = false;
|
|
for (int j = 0; j < functionType.namedParameters.length; ++j) {
|
|
if (argument.name == functionType.namedParameters[j].name) {
|
|
var expectedType = substitution.substituteType(
|
|
functionType.namedParameters[j].type,
|
|
contravariant: true);
|
|
argument.value =
|
|
checkAndDowncastExpression(argument.value, expectedType);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
fail(argument.value, 'Unexpected named parameter: ${argument.name}');
|
|
return const BottomType();
|
|
}
|
|
}
|
|
return substitution.substituteType(functionType.returnType);
|
|
} else {
|
|
// Note: attempting to resolve .call() on [functionType] could lead to an
|
|
// infinite regress, so just assume `dynamic`.
|
|
return const DynamicType();
|
|
}
|
|
}
|
|
|
|
DartType _getInternalReturnType(FunctionNode function) {
|
|
switch (function.asyncMarker) {
|
|
case AsyncMarker.Sync:
|
|
return function.returnType;
|
|
|
|
case AsyncMarker.Async:
|
|
Class container = coreTypes.futureClass;
|
|
DartType returnType = function.returnType;
|
|
if (returnType is InterfaceType && returnType.classNode == container) {
|
|
return returnType.typeArguments.single;
|
|
}
|
|
return const DynamicType();
|
|
|
|
case AsyncMarker.SyncStar:
|
|
case AsyncMarker.AsyncStar:
|
|
return null;
|
|
|
|
case AsyncMarker.SyncYielding:
|
|
TreeNode parent = function.parent;
|
|
while (parent is! FunctionNode) {
|
|
parent = parent.parent;
|
|
}
|
|
final enclosingFunction = parent as FunctionNode;
|
|
if (enclosingFunction.dartAsyncMarker == AsyncMarker.SyncStar) {
|
|
return coreTypes.boolClass.rawType;
|
|
}
|
|
return null;
|
|
|
|
default:
|
|
throw 'Unexpected async marker: ${function.asyncMarker}';
|
|
}
|
|
}
|
|
|
|
DartType _getYieldType(FunctionNode function) {
|
|
switch (function.asyncMarker) {
|
|
case AsyncMarker.Sync:
|
|
case AsyncMarker.Async:
|
|
return null;
|
|
|
|
case AsyncMarker.SyncStar:
|
|
case AsyncMarker.AsyncStar:
|
|
Class container = function.asyncMarker == AsyncMarker.SyncStar
|
|
? coreTypes.iterableClass
|
|
: coreTypes.streamClass;
|
|
DartType returnType = function.returnType;
|
|
if (returnType is InterfaceType && returnType.classNode == container) {
|
|
return returnType.typeArguments.single;
|
|
}
|
|
return const DynamicType();
|
|
|
|
case AsyncMarker.SyncYielding:
|
|
return function.returnType;
|
|
|
|
default:
|
|
throw 'Unexpected async marker: ${function.asyncMarker}';
|
|
}
|
|
}
|
|
|
|
Substitution _instantiateFunction(List<TypeParameter> typeParameters,
|
|
List<DartType> typeArguments, TreeNode where,
|
|
{Substitution receiverSubstitution}) {
|
|
var instantiation = Substitution.fromPairs(typeParameters, typeArguments);
|
|
var substitution = receiverSubstitution == null
|
|
? instantiation
|
|
: Substitution.combine(receiverSubstitution, instantiation);
|
|
for (int i = 0; i < typeParameters.length; ++i) {
|
|
var argument = typeArguments[i];
|
|
var bound = substitution.substituteType(typeParameters[i].bound);
|
|
checkAssignable(where, argument, bound);
|
|
}
|
|
return substitution;
|
|
}
|
|
|
|
@override
|
|
DartType visitAsExpression(AsExpression node) {
|
|
visitExpression(node.operand);
|
|
return node.type;
|
|
}
|
|
|
|
@override
|
|
DartType visitAwaitExpression(AwaitExpression node) {
|
|
return environment.unfutureType(visitExpression(node.operand));
|
|
}
|
|
|
|
@override
|
|
DartType visitBoolLiteral(BoolLiteral node) {
|
|
return environment.boolType;
|
|
}
|
|
|
|
@override
|
|
DartType visitConditionalExpression(ConditionalExpression node) {
|
|
node.condition =
|
|
checkAndDowncastExpression(node.condition, environment.boolType);
|
|
node.then = checkAndDowncastExpression(node.then, node.staticType);
|
|
node.otherwise =
|
|
checkAndDowncastExpression(node.otherwise, node.staticType);
|
|
return node.staticType;
|
|
}
|
|
|
|
@override
|
|
DartType visitConstructorInvocation(ConstructorInvocation node) {
|
|
Constructor target = node.target;
|
|
Arguments arguments = node.arguments;
|
|
Class class_ = target.enclosingClass;
|
|
handleCall(arguments, target.function.functionType,
|
|
typeParameters: class_.typeParameters);
|
|
return new InterfaceType(target.enclosingClass, arguments.types);
|
|
}
|
|
|
|
@override
|
|
DartType visitDirectMethodInvocation(DirectMethodInvocation node) {
|
|
return handleCall(node.arguments, node.target.getterType,
|
|
receiver: getReceiverType(node, node.receiver, node.target));
|
|
}
|
|
|
|
@override
|
|
DartType visitDirectPropertyGet(DirectPropertyGet node) {
|
|
var receiver = getReceiverType(node, node.receiver, node.target);
|
|
return receiver.substituteType(node.target.getterType);
|
|
}
|
|
|
|
@override
|
|
DartType visitDirectPropertySet(DirectPropertySet node) {
|
|
var receiver = getReceiverType(node, node.receiver, node.target);
|
|
var value = visitExpression(node.value);
|
|
checkAssignable(node, value,
|
|
receiver.substituteType(node.target.setterType, contravariant: true));
|
|
return value;
|
|
}
|
|
|
|
@override
|
|
DartType visitDoubleLiteral(DoubleLiteral node) {
|
|
return environment.doubleType;
|
|
}
|
|
|
|
@override
|
|
DartType visitFunctionExpression(FunctionExpression node) {
|
|
handleNestedFunctionNode(node.function);
|
|
return node.function.functionType;
|
|
}
|
|
|
|
@override
|
|
DartType visitIntLiteral(IntLiteral node) {
|
|
return environment.intType;
|
|
}
|
|
|
|
@override
|
|
DartType visitInvalidExpression(InvalidExpression node) {
|
|
return const DynamicType();
|
|
}
|
|
|
|
@override
|
|
DartType visitIsExpression(IsExpression node) {
|
|
visitExpression(node.operand);
|
|
return environment.boolType;
|
|
}
|
|
|
|
@override
|
|
DartType visitLet(Let node) {
|
|
var value = visitExpression(node.variable.initializer);
|
|
if (node.variable.type is DynamicType) {
|
|
node.variable.type = value;
|
|
}
|
|
return visitExpression(node.body);
|
|
}
|
|
|
|
@override
|
|
DartType visitInstantiation(Instantiation node) {
|
|
DartType type = visitExpression(node.expression);
|
|
if (type is! FunctionType) {
|
|
fail(node, 'Not a function type: $type');
|
|
return const BottomType();
|
|
}
|
|
FunctionType functionType = type;
|
|
if (functionType.typeParameters.length != node.typeArguments.length) {
|
|
fail(node, 'Wrong number of type arguments');
|
|
return const BottomType();
|
|
}
|
|
return _instantiateFunction(
|
|
functionType.typeParameters, node.typeArguments, node)
|
|
.substituteType(functionType.withoutTypeParameters);
|
|
}
|
|
|
|
@override
|
|
DartType visitListLiteral(ListLiteral node) {
|
|
for (int i = 0; i < node.expressions.length; ++i) {
|
|
node.expressions[i] =
|
|
checkAndDowncastExpression(node.expressions[i], node.typeArgument);
|
|
}
|
|
return environment.literalListType(node.typeArgument);
|
|
}
|
|
|
|
@override
|
|
DartType visitLogicalExpression(LogicalExpression node) {
|
|
node.left = checkAndDowncastExpression(node.left, environment.boolType);
|
|
node.right = checkAndDowncastExpression(node.right, environment.boolType);
|
|
return environment.boolType;
|
|
}
|
|
|
|
@override
|
|
DartType visitMapLiteral(MapLiteral node) {
|
|
for (var entry in node.entries) {
|
|
entry.key = checkAndDowncastExpression(entry.key, node.keyType);
|
|
entry.value = checkAndDowncastExpression(entry.value, node.valueType);
|
|
}
|
|
return environment.literalMapType(node.keyType, node.valueType);
|
|
}
|
|
|
|
DartType handleDynamicCall(DartType receiver, Arguments arguments) {
|
|
arguments.positional.forEach(visitExpression);
|
|
arguments.named.forEach((NamedExpression n) => visitExpression(n.value));
|
|
return const DynamicType();
|
|
}
|
|
|
|
DartType handleFunctionCall(
|
|
TreeNode access, FunctionType function, Arguments arguments) {
|
|
if (function.requiredParameterCount > arguments.positional.length) {
|
|
fail(access, 'Too few positional arguments');
|
|
return const BottomType();
|
|
}
|
|
if (function.positionalParameters.length < arguments.positional.length) {
|
|
fail(access, 'Too many positional arguments');
|
|
return const BottomType();
|
|
}
|
|
if (function.typeParameters.length != arguments.types.length) {
|
|
fail(access, 'Wrong number of type arguments');
|
|
return const BottomType();
|
|
}
|
|
var instantiation =
|
|
Substitution.fromPairs(function.typeParameters, arguments.types);
|
|
for (int i = 0; i < arguments.positional.length; ++i) {
|
|
var expectedType = instantiation.substituteType(
|
|
function.positionalParameters[i],
|
|
contravariant: true);
|
|
arguments.positional[i] =
|
|
checkAndDowncastExpression(arguments.positional[i], expectedType);
|
|
}
|
|
for (int i = 0; i < arguments.named.length; ++i) {
|
|
var argument = arguments.named[i];
|
|
var parameterType = function.getNamedParameter(argument.name);
|
|
if (parameterType != null) {
|
|
var expectedType =
|
|
instantiation.substituteType(parameterType, contravariant: true);
|
|
argument.value =
|
|
checkAndDowncastExpression(argument.value, expectedType);
|
|
} else {
|
|
fail(argument.value, 'Unexpected named parameter: ${argument.name}');
|
|
return const BottomType();
|
|
}
|
|
}
|
|
return instantiation.substituteType(function.returnType);
|
|
}
|
|
|
|
@override
|
|
DartType visitMethodInvocation(MethodInvocation node) {
|
|
var target = node.interfaceTarget;
|
|
if (target == null) {
|
|
var receiver = visitExpression(node.receiver);
|
|
if (node.name.name == '==') {
|
|
visitExpression(node.arguments.positional.single);
|
|
return environment.boolType;
|
|
}
|
|
if (node.name.name == 'call' && receiver is FunctionType) {
|
|
return handleFunctionCall(node, receiver, node.arguments);
|
|
}
|
|
checkUnresolvedInvocation(receiver, node);
|
|
return handleDynamicCall(receiver, node.arguments);
|
|
} else if (target is Procedure &&
|
|
environment.isOverloadedArithmeticOperator(target)) {
|
|
assert(node.arguments.positional.length == 1);
|
|
var receiver = visitExpression(node.receiver);
|
|
var argument = visitExpression(node.arguments.positional[0]);
|
|
return environment.getTypeOfOverloadedArithmetic(receiver, argument);
|
|
} else {
|
|
return handleCall(node.arguments, target.getterType,
|
|
receiver: getReceiverType(node, node.receiver, node.interfaceTarget));
|
|
}
|
|
}
|
|
|
|
@override
|
|
DartType visitPropertyGet(PropertyGet node) {
|
|
if (node.interfaceTarget == null) {
|
|
final receiver = visitExpression(node.receiver);
|
|
checkUnresolvedInvocation(receiver, node);
|
|
return const DynamicType();
|
|
} else {
|
|
var receiver = getReceiverType(node, node.receiver, node.interfaceTarget);
|
|
return receiver.substituteType(node.interfaceTarget.getterType);
|
|
}
|
|
}
|
|
|
|
@override
|
|
DartType visitPropertySet(PropertySet node) {
|
|
var value = visitExpression(node.value);
|
|
if (node.interfaceTarget != null) {
|
|
var receiver = getReceiverType(node, node.receiver, node.interfaceTarget);
|
|
checkAssignable(
|
|
node.value,
|
|
value,
|
|
receiver.substituteType(node.interfaceTarget.setterType,
|
|
contravariant: true));
|
|
} else {
|
|
final receiver = visitExpression(node.receiver);
|
|
checkUnresolvedInvocation(receiver, node);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
@override
|
|
DartType visitNot(Not node) {
|
|
visitExpression(node.operand);
|
|
return environment.boolType;
|
|
}
|
|
|
|
@override
|
|
DartType visitNullLiteral(NullLiteral node) {
|
|
return const BottomType();
|
|
}
|
|
|
|
@override
|
|
DartType visitRethrow(Rethrow node) {
|
|
return const BottomType();
|
|
}
|
|
|
|
@override
|
|
DartType visitStaticGet(StaticGet node) {
|
|
return node.target.getterType;
|
|
}
|
|
|
|
@override
|
|
DartType visitStaticInvocation(StaticInvocation node) {
|
|
return handleCall(node.arguments, node.target.getterType);
|
|
}
|
|
|
|
@override
|
|
DartType visitStaticSet(StaticSet node) {
|
|
var value = visitExpression(node.value);
|
|
checkAssignable(node.value, value, node.target.setterType);
|
|
return value;
|
|
}
|
|
|
|
@override
|
|
DartType visitStringConcatenation(StringConcatenation node) {
|
|
node.expressions.forEach(visitExpression);
|
|
return environment.stringType;
|
|
}
|
|
|
|
@override
|
|
DartType visitStringLiteral(StringLiteral node) {
|
|
return environment.stringType;
|
|
}
|
|
|
|
@override
|
|
DartType visitSuperMethodInvocation(SuperMethodInvocation node) {
|
|
if (node.interfaceTarget == null) {
|
|
checkUnresolvedInvocation(environment.thisType, node);
|
|
return handleDynamicCall(environment.thisType, node.arguments);
|
|
} else {
|
|
return handleCall(node.arguments, node.interfaceTarget.getterType,
|
|
receiver: getSuperReceiverType(node.interfaceTarget));
|
|
}
|
|
}
|
|
|
|
@override
|
|
DartType visitSuperPropertyGet(SuperPropertyGet node) {
|
|
if (node.interfaceTarget == null) {
|
|
checkUnresolvedInvocation(environment.thisType, node);
|
|
return const DynamicType();
|
|
} else {
|
|
var receiver = getSuperReceiverType(node.interfaceTarget);
|
|
return receiver.substituteType(node.interfaceTarget.getterType);
|
|
}
|
|
}
|
|
|
|
@override
|
|
DartType visitSuperPropertySet(SuperPropertySet node) {
|
|
var value = visitExpression(node.value);
|
|
if (node.interfaceTarget != null) {
|
|
var receiver = getSuperReceiverType(node.interfaceTarget);
|
|
checkAssignable(
|
|
node.value,
|
|
value,
|
|
receiver.substituteType(node.interfaceTarget.setterType,
|
|
contravariant: true));
|
|
} else {
|
|
checkUnresolvedInvocation(environment.thisType, node);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
@override
|
|
DartType visitSymbolLiteral(SymbolLiteral node) {
|
|
return environment.symbolType;
|
|
}
|
|
|
|
@override
|
|
DartType visitThisExpression(ThisExpression node) {
|
|
return environment.thisType;
|
|
}
|
|
|
|
@override
|
|
DartType visitThrow(Throw node) {
|
|
visitExpression(node.expression);
|
|
return const BottomType();
|
|
}
|
|
|
|
@override
|
|
DartType visitTypeLiteral(TypeLiteral node) {
|
|
return environment.typeType;
|
|
}
|
|
|
|
@override
|
|
DartType visitVariableGet(VariableGet node) {
|
|
return node.promotedType ?? node.variable.type;
|
|
}
|
|
|
|
@override
|
|
DartType visitVariableSet(VariableSet node) {
|
|
var value = visitExpression(node.value);
|
|
checkAssignable(node.value, value, node.variable.type);
|
|
return value;
|
|
}
|
|
|
|
@override
|
|
DartType visitLoadLibrary(LoadLibrary node) {
|
|
return environment.futureType(const DynamicType());
|
|
}
|
|
|
|
@override
|
|
DartType visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) {
|
|
return environment.objectType;
|
|
}
|
|
|
|
@override
|
|
visitAssertStatement(AssertStatement node) {
|
|
visitExpression(node.condition);
|
|
if (node.message != null) {
|
|
visitExpression(node.message);
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitBlock(Block node) {
|
|
node.statements.forEach(visitStatement);
|
|
}
|
|
|
|
@override
|
|
visitAssertBlock(AssertBlock node) {
|
|
node.statements.forEach(visitStatement);
|
|
}
|
|
|
|
@override
|
|
visitBreakStatement(BreakStatement node) {}
|
|
|
|
@override
|
|
visitContinueSwitchStatement(ContinueSwitchStatement node) {}
|
|
|
|
@override
|
|
visitDoStatement(DoStatement node) {
|
|
visitStatement(node.body);
|
|
node.condition =
|
|
checkAndDowncastExpression(node.condition, environment.boolType);
|
|
}
|
|
|
|
@override
|
|
visitEmptyStatement(EmptyStatement node) {}
|
|
|
|
@override
|
|
visitExpressionStatement(ExpressionStatement node) {
|
|
visitExpression(node.expression);
|
|
}
|
|
|
|
@override
|
|
visitForInStatement(ForInStatement node) {
|
|
var iterable = visitExpression(node.iterable);
|
|
// TODO(asgerf): Store interface targets on for-in loops or desugar them,
|
|
// instead of doing the ad-hoc resolution here.
|
|
if (node.isAsync) {
|
|
checkAssignable(node, getStreamElementType(iterable), node.variable.type);
|
|
} else {
|
|
checkAssignable(
|
|
node, getIterableElementType(iterable), node.variable.type);
|
|
}
|
|
visitStatement(node.body);
|
|
}
|
|
|
|
static final Name iteratorName = new Name('iterator');
|
|
static final Name currentName = new Name('current');
|
|
|
|
DartType getIterableElementType(DartType iterable) {
|
|
if (iterable is InterfaceType) {
|
|
var iteratorGetter =
|
|
hierarchy.getInterfaceMember(iterable.classNode, iteratorName);
|
|
if (iteratorGetter == null) return const DynamicType();
|
|
var castedIterable = hierarchy.getTypeAsInstanceOf(
|
|
iterable, iteratorGetter.enclosingClass);
|
|
var iteratorType = Substitution
|
|
.fromInterfaceType(castedIterable)
|
|
.substituteType(iteratorGetter.getterType);
|
|
if (iteratorType is InterfaceType) {
|
|
var currentGetter =
|
|
hierarchy.getInterfaceMember(iteratorType.classNode, currentName);
|
|
if (currentGetter == null) return const DynamicType();
|
|
var castedIteratorType = hierarchy.getTypeAsInstanceOf(
|
|
iteratorType, currentGetter.enclosingClass);
|
|
return Substitution
|
|
.fromInterfaceType(castedIteratorType)
|
|
.substituteType(currentGetter.getterType);
|
|
}
|
|
}
|
|
return const DynamicType();
|
|
}
|
|
|
|
DartType getStreamElementType(DartType stream) {
|
|
if (stream is InterfaceType) {
|
|
var asStream =
|
|
hierarchy.getTypeAsInstanceOf(stream, coreTypes.streamClass);
|
|
if (asStream == null) return const DynamicType();
|
|
return asStream.typeArguments.single;
|
|
}
|
|
return const DynamicType();
|
|
}
|
|
|
|
@override
|
|
visitForStatement(ForStatement node) {
|
|
node.variables.forEach(visitVariableDeclaration);
|
|
if (node.condition != null) {
|
|
node.condition =
|
|
checkAndDowncastExpression(node.condition, environment.boolType);
|
|
}
|
|
node.updates.forEach(visitExpression);
|
|
visitStatement(node.body);
|
|
}
|
|
|
|
@override
|
|
visitFunctionDeclaration(FunctionDeclaration node) {
|
|
handleNestedFunctionNode(node.function);
|
|
}
|
|
|
|
@override
|
|
visitIfStatement(IfStatement node) {
|
|
node.condition =
|
|
checkAndDowncastExpression(node.condition, environment.boolType);
|
|
visitStatement(node.then);
|
|
if (node.otherwise != null) {
|
|
visitStatement(node.otherwise);
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitLabeledStatement(LabeledStatement node) {
|
|
visitStatement(node.body);
|
|
}
|
|
|
|
@override
|
|
visitReturnStatement(ReturnStatement node) {
|
|
if (node.expression != null) {
|
|
if (environment.returnType == null) {
|
|
fail(node, 'Return of a value from void method');
|
|
} else {
|
|
var type = visitExpression(node.expression);
|
|
if (environment.currentAsyncMarker == AsyncMarker.Async) {
|
|
type = environment.unfutureType(type);
|
|
}
|
|
checkAssignable(node.expression, type, environment.returnType);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitSwitchStatement(SwitchStatement node) {
|
|
visitExpression(node.expression);
|
|
for (var switchCase in node.cases) {
|
|
switchCase.expressions.forEach(visitExpression);
|
|
visitStatement(switchCase.body);
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitTryCatch(TryCatch node) {
|
|
visitStatement(node.body);
|
|
for (var catchClause in node.catches) {
|
|
visitStatement(catchClause.body);
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitTryFinally(TryFinally node) {
|
|
visitStatement(node.body);
|
|
visitStatement(node.finalizer);
|
|
}
|
|
|
|
@override
|
|
visitVariableDeclaration(VariableDeclaration node) {
|
|
if (node.initializer != null) {
|
|
node.initializer =
|
|
checkAndDowncastExpression(node.initializer, node.type);
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitWhileStatement(WhileStatement node) {
|
|
node.condition =
|
|
checkAndDowncastExpression(node.condition, environment.boolType);
|
|
visitStatement(node.body);
|
|
}
|
|
|
|
@override
|
|
visitYieldStatement(YieldStatement node) {
|
|
if (node.isYieldStar) {
|
|
Class container = environment.currentAsyncMarker == AsyncMarker.AsyncStar
|
|
? coreTypes.streamClass
|
|
: coreTypes.iterableClass;
|
|
var type = visitExpression(node.expression);
|
|
var asContainer = type is InterfaceType
|
|
? hierarchy.getTypeAsInstanceOf(type, container)
|
|
: null;
|
|
if (asContainer != null) {
|
|
checkAssignable(node.expression, asContainer.typeArguments[0],
|
|
environment.yieldType);
|
|
} else {
|
|
fail(node.expression, '$type is not an instance of $container');
|
|
}
|
|
} else {
|
|
node.expression =
|
|
checkAndDowncastExpression(node.expression, environment.yieldType);
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitFieldInitializer(FieldInitializer node) {
|
|
node.value = checkAndDowncastExpression(node.value, node.field.type);
|
|
}
|
|
|
|
@override
|
|
visitRedirectingInitializer(RedirectingInitializer node) {
|
|
handleCall(node.arguments, node.target.getterType,
|
|
typeParameters: const <TypeParameter>[]);
|
|
}
|
|
|
|
@override
|
|
visitSuperInitializer(SuperInitializer node) {
|
|
handleCall(node.arguments, node.target.getterType,
|
|
typeParameters: const <TypeParameter>[],
|
|
receiver: getSuperReceiverType(node.target));
|
|
}
|
|
|
|
@override
|
|
visitLocalInitializer(LocalInitializer node) {
|
|
visitVariableDeclaration(node.variable);
|
|
}
|
|
|
|
@override
|
|
visitAssertInitializer(AssertInitializer node) {
|
|
visitAssertStatement(node.statement);
|
|
}
|
|
|
|
@override
|
|
visitInvalidInitializer(InvalidInitializer node) {}
|
|
|
|
@override
|
|
visitConstantExpression(ConstantExpression node) {
|
|
// Without explicitly running the "constants" transformation, we should
|
|
// never get here!
|
|
throw 'unreachable';
|
|
}
|
|
}
|