blob: 1ca3593b0e7cf4e0710d47a3d511ee05466988a6 [file] [log] [blame]
// Copyright (c) 2012, 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.
package com.google.dart.compiler.type;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.dart.compiler.CommandLineOptions.CompilerOptions;
import com.google.dart.compiler.DartCompilationError;
import com.google.dart.compiler.DartCompilationPhase;
import com.google.dart.compiler.DartCompilerContext;
import com.google.dart.compiler.ErrorCode;
import com.google.dart.compiler.ErrorSeverity;
import com.google.dart.compiler.PackageLibraryManager;
import com.google.dart.compiler.Source;
import com.google.dart.compiler.ast.ASTNodes;
import com.google.dart.compiler.ast.ASTVisitor;
import com.google.dart.compiler.ast.DartArrayAccess;
import com.google.dart.compiler.ast.DartArrayLiteral;
import com.google.dart.compiler.ast.DartAssertStatement;
import com.google.dart.compiler.ast.DartBinaryExpression;
import com.google.dart.compiler.ast.DartBlock;
import com.google.dart.compiler.ast.DartBooleanLiteral;
import com.google.dart.compiler.ast.DartBreakStatement;
import com.google.dart.compiler.ast.DartCascadeExpression;
import com.google.dart.compiler.ast.DartCase;
import com.google.dart.compiler.ast.DartCatchBlock;
import com.google.dart.compiler.ast.DartClass;
import com.google.dart.compiler.ast.DartComment;
import com.google.dart.compiler.ast.DartConditional;
import com.google.dart.compiler.ast.DartContinueStatement;
import com.google.dart.compiler.ast.DartDeclaration;
import com.google.dart.compiler.ast.DartDefault;
import com.google.dart.compiler.ast.DartDoWhileStatement;
import com.google.dart.compiler.ast.DartDoubleLiteral;
import com.google.dart.compiler.ast.DartEmptyStatement;
import com.google.dart.compiler.ast.DartExportDirective;
import com.google.dart.compiler.ast.DartExprStmt;
import com.google.dart.compiler.ast.DartExpression;
import com.google.dart.compiler.ast.DartField;
import com.google.dart.compiler.ast.DartFieldDefinition;
import com.google.dart.compiler.ast.DartForInStatement;
import com.google.dart.compiler.ast.DartForStatement;
import com.google.dart.compiler.ast.DartFunction;
import com.google.dart.compiler.ast.DartFunctionExpression;
import com.google.dart.compiler.ast.DartFunctionObjectInvocation;
import com.google.dart.compiler.ast.DartFunctionTypeAlias;
import com.google.dart.compiler.ast.DartIdentifier;
import com.google.dart.compiler.ast.DartIfStatement;
import com.google.dart.compiler.ast.DartImportDirective;
import com.google.dart.compiler.ast.DartInitializer;
import com.google.dart.compiler.ast.DartIntegerLiteral;
import com.google.dart.compiler.ast.DartInvocation;
import com.google.dart.compiler.ast.DartLabel;
import com.google.dart.compiler.ast.DartLibraryDirective;
import com.google.dart.compiler.ast.DartLiteral;
import com.google.dart.compiler.ast.DartMapLiteral;
import com.google.dart.compiler.ast.DartMapLiteralEntry;
import com.google.dart.compiler.ast.DartMethodDefinition;
import com.google.dart.compiler.ast.DartMethodInvocation;
import com.google.dart.compiler.ast.DartNamedExpression;
import com.google.dart.compiler.ast.DartNativeBlock;
import com.google.dart.compiler.ast.DartNativeDirective;
import com.google.dart.compiler.ast.DartNewExpression;
import com.google.dart.compiler.ast.DartNode;
import com.google.dart.compiler.ast.DartNullLiteral;
import com.google.dart.compiler.ast.DartParameter;
import com.google.dart.compiler.ast.DartParameterizedTypeNode;
import com.google.dart.compiler.ast.DartParenthesizedExpression;
import com.google.dart.compiler.ast.DartPropertyAccess;
import com.google.dart.compiler.ast.DartRedirectConstructorInvocation;
import com.google.dart.compiler.ast.DartReturnStatement;
import com.google.dart.compiler.ast.DartSourceDirective;
import com.google.dart.compiler.ast.DartStatement;
import com.google.dart.compiler.ast.DartStringInterpolation;
import com.google.dart.compiler.ast.DartStringLiteral;
import com.google.dart.compiler.ast.DartSuperConstructorInvocation;
import com.google.dart.compiler.ast.DartSuperExpression;
import com.google.dart.compiler.ast.DartSwitchMember;
import com.google.dart.compiler.ast.DartSwitchStatement;
import com.google.dart.compiler.ast.DartSyntheticErrorExpression;
import com.google.dart.compiler.ast.DartSyntheticErrorIdentifier;
import com.google.dart.compiler.ast.DartSyntheticErrorStatement;
import com.google.dart.compiler.ast.DartThisExpression;
import com.google.dart.compiler.ast.DartThrowExpression;
import com.google.dart.compiler.ast.DartTryStatement;
import com.google.dart.compiler.ast.DartTypeExpression;
import com.google.dart.compiler.ast.DartTypeNode;
import com.google.dart.compiler.ast.DartTypeParameter;
import com.google.dart.compiler.ast.DartUnaryExpression;
import com.google.dart.compiler.ast.DartUnit;
import com.google.dart.compiler.ast.DartUnqualifiedInvocation;
import com.google.dart.compiler.ast.DartVariable;
import com.google.dart.compiler.ast.DartVariableStatement;
import com.google.dart.compiler.ast.DartWhileStatement;
import com.google.dart.compiler.ast.Modifiers;
import com.google.dart.compiler.common.HasSourceInfo;
import com.google.dart.compiler.common.SourceInfo;
import com.google.dart.compiler.parser.Token;
import com.google.dart.compiler.resolver.ClassElement;
import com.google.dart.compiler.resolver.ClassNodeElement;
import com.google.dart.compiler.resolver.ConstructorElement;
import com.google.dart.compiler.resolver.CoreTypeProvider;
import com.google.dart.compiler.resolver.CyclicDeclarationException;
import com.google.dart.compiler.resolver.Element;
import com.google.dart.compiler.resolver.ElementKind;
import com.google.dart.compiler.resolver.Elements;
import com.google.dart.compiler.resolver.FieldElement;
import com.google.dart.compiler.resolver.FunctionAliasElement;
import com.google.dart.compiler.resolver.MethodElement;
import com.google.dart.compiler.resolver.NodeElement;
import com.google.dart.compiler.resolver.ResolverErrorCode;
import com.google.dart.compiler.resolver.TypeErrorCode;
import com.google.dart.compiler.resolver.VariableElement;
import com.google.dart.compiler.type.InterfaceType.Member;
import com.google.dart.compiler.util.apache.ObjectUtils;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Analyzer of static type information.
*/
public class TypeAnalyzer implements DartCompilationPhase {
private final Set<ClassElement> diagnosedAbstractClasses = Sets.newHashSet();
/**
* Perform type analysis on the given AST rooted at <code>node</code>.
*
* @param node The root of the tree to analyze
* @param typeProvider The source of pre-defined type definitions
* @param context The compilation context (DartCompilerContext)
* @param currentClass The class that contains <code>node</code>. Will be null
* for top-level declarations.
* @return The type of <code>node</code>.
*/
public static Type analyze(DartNode node, CoreTypeProvider typeProvider,
DartCompilerContext context, InterfaceType currentClass) {
Set<ClassElement> diagnosed = Sets.newHashSet();
Analyzer analyzer = new Analyzer(context, typeProvider, diagnosed);
analyzer.setCurrentClass(currentClass);
return node.accept(analyzer);
}
@Override
public DartUnit exec(DartUnit unit, DartCompilerContext context, CoreTypeProvider typeProvider) {
unit.accept(new Analyzer(context, typeProvider, diagnosedAbstractClasses));
return unit;
}
@VisibleForTesting
static class Analyzer extends ASTVisitor<Type> {
private final DynamicType dynamicType;
private final Type stringType;
private final InterfaceType defaultLiteralMapType;
private final Type voidType;
private final DartCompilerContext context;
private final Types types;
private Type expected;
private MethodElement currentMethod;
private InterfaceType currentClass;
private final InterfaceType objectType;
private final InterfaceType boolType;
private final InterfaceType numType;
private final InterfaceType intType;
private final InterfaceType doubleType;
private final Type nullType;
private final InterfaceType functionType;
private final InterfaceType dynamicIteratorType;
private final boolean developerModeChecks;
private final boolean suppressSdkWarnings;
private final boolean typeChecksForInferredTypes;
private final boolean reportNoMemberWhenHasInterceptor;
private final Map<DartBlock, VariableElementsRestorer> restoreOnBlockExit = Maps.newHashMap();
/**
* When we see variable assignment, we remember here old {@link Type} (if not done already) and
* set new {@link Type} into {@link VariableElement}. On the exit from basic block we remove
* first element and merge new types with old types.
*/
private final LinkedList<BlockTypeContext> blockOldTypes = Lists.newLinkedList();
private static class BlockTypeContext {
final Map<VariableElement, Type> oldTypes = Maps.newHashMap();
final Map<VariableElement, Type> newTypes = Maps.newHashMap();
void rememberOldType(VariableElement element, Type oriType) {
if (!oldTypes.containsKey(element)) {
oldTypes.put(element, oriType);
}
}
void setType(VariableElement element, Type newType) {
if (canSetType(element)) {
rememberOldType(element, element.getType());
newTypes.put(element, newType);
Elements.setType(element, newType);
}
}
boolean canSetType(VariableElement element) {
Type type = element.getType();
// no type declared, no assignment yet
if (TypeKind.of(type) == TypeKind.DYNAMIC) {
return true;
}
// was assignment, inferred
if (type != null && TypeQuality.isInferred(type)) {
return true;
}
// was declared with type, keep it
return false;
}
Map<VariableElement, Type> getNewTypesAndRestoreOld() {
for (Entry<VariableElement, Type> entry : oldTypes.entrySet()) {
VariableElement variable = entry.getKey();
Elements.setType(variable, entry.getValue());
}
return newTypes;
}
}
/**
* Keeps track of the number of nested catches, used to detect re-throws
* outside of any catch block.
*/
private int catchDepth = 0;
Analyzer(DartCompilerContext context, CoreTypeProvider typeProvider,
Set<ClassElement> diagnosedAbstractClasses) {
this.context = context;
this.developerModeChecks = context.getCompilerConfiguration().developerModeChecks();
this.types = Types.getInstance(typeProvider);
this.dynamicType = typeProvider.getDynamicType();
this.stringType = typeProvider.getStringType();
this.defaultLiteralMapType = typeProvider.getMapType(stringType, dynamicType);
this.voidType = typeProvider.getVoidType();
this.objectType = typeProvider.getObjectType();
this.boolType = typeProvider.getBoolType();
this.numType = typeProvider.getNumType();
this.intType = typeProvider.getIntType();
this.doubleType = typeProvider.getDoubleType();
this.nullType = typeProvider.getNullType();
this.functionType = typeProvider.getFunctionType();
this.dynamicIteratorType = typeProvider.getIteratorType(dynamicType);
CompilerOptions compilerOptions = context.getCompilerConfiguration().getCompilerOptions();
this.suppressSdkWarnings = compilerOptions.suppressSdkWarnings();
this.typeChecksForInferredTypes = compilerOptions.typeChecksForInferredTypes();
this.reportNoMemberWhenHasInterceptor = compilerOptions.reportNoMemberWhenHasInterceptor();
}
@VisibleForTesting
void setCurrentClass(InterfaceType type) {
currentClass = type;
}
@VisibleForTesting
void pushBasicBlockContext() {
blockOldTypes.addFirst(new BlockTypeContext());
}
private InterfaceType getCurrentClass() {
return currentClass;
}
private DynamicType typeError(HasSourceInfo node, ErrorCode code, Object... arguments) {
onError(node, code, arguments);
return dynamicType;
}
private void onError(HasSourceInfo node, ErrorCode errorCode, Object... arguments) {
onError(node.getSourceInfo(), errorCode, arguments);
}
private void onError(SourceInfo errorTarget, ErrorCode errorCode, Object... arguments) {
if (suppressSdkWarnings && errorCode.getErrorSeverity() == ErrorSeverity.WARNING) {
Source source = errorTarget.getSource();
if (source != null && PackageLibraryManager.isDartUri(source.getUri())) {
return;
}
}
context.onError(new DartCompilationError(errorTarget, errorCode, arguments));
}
AssertionError internalError(HasSourceInfo node, String message, Object... arguments) {
message = String.format(message, arguments);
context.onError(new DartCompilationError(node, TypeErrorCode.INTERNAL_ERROR,
message));
return new AssertionError("Internal error: " + message);
}
private Type typeOfLiteral(DartLiteral node) {
Type type = node.getType();
return type == null ? voidType : type;
}
private Token getBasicOperator(DartNode diagnosticNode, Token op) {
switch(op) {
case INC:
return Token.ADD;
case DEC:
return Token.SUB;
case ASSIGN_BIT_OR:
return Token.BIT_OR;
case ASSIGN_BIT_XOR:
return Token.BIT_XOR;
case ASSIGN_BIT_AND:
return Token.BIT_AND;
case ASSIGN_SHL:
return Token.SHL;
case ASSIGN_SAR:
return Token.SAR;
case ASSIGN_ADD:
return Token.ADD;
case ASSIGN_SUB:
return Token.SUB;
case ASSIGN_MUL:
return Token.MUL;
case ASSIGN_DIV:
return Token.DIV;
case ASSIGN_MOD:
return Token.MOD;
case ASSIGN_TRUNC:
return Token.TRUNC;
default:
internalError(diagnosticNode, "unexpected operator %s", op.name());
return null;
}
}
@Override
public Type visitRedirectConstructorInvocation(DartRedirectConstructorInvocation node) {
return checkConstructorForwarding(node, node.getElement());
}
private String methodNameForUnaryOperator(DartNode diagnosticNode, Token operator) {
if (operator == Token.SUB) {
return "operator -";
} else if (operator == Token.BIT_NOT) {
return "operator ~";
}
return "operator " + getBasicOperator(diagnosticNode, operator).getSyntax();
}
private String methodNameForBinaryOperator(Token operator) {
if (operator.getSyntax().equals("-")) {
return "operator -binary";
}
return "operator " + operator.getSyntax();
}
private Type analyzeBinaryOperator(DartNode node, Type lhsType, Token operator,
DartNode diagnosticNode, DartExpression rhs) {
Type rhsType = nonVoidTypeOf(rhs);
String methodName = methodNameForBinaryOperator(operator);
HasSourceInfo problemTarget = getOperatorHasSourceInfo(node);
Member member = lookupMember(lhsType, methodName, problemTarget);
if (member != null) {
Element element = member.getElement();
node.setElement(element);
FunctionType methodType = getMethodType(lhsType, member, methodName, diagnosticNode);
checkDeprecated(problemTarget, element);
Type returnType = checkInvocation(Collections.<DartExpression> singletonList(rhs),
diagnosticNode, methodName, methodType, null);
// tweak return type for int/int and int/double operators
{
boolean lhsInt = intType.equals(lhsType);
boolean rhsInt = intType.equals(rhsType);
boolean lhsDouble = doubleType.equals(lhsType);
boolean rhsDouble = doubleType.equals(rhsType);
switch (operator) {
case ADD:
case SUB:
case MUL:
case TRUNC:
case MOD:
if (lhsInt && rhsInt) {
return intType;
}
case DIV:
if (lhsDouble || rhsDouble) {
return doubleType;
}
}
}
// done
return returnType;
} else {
return dynamicType;
}
}
private Type analyzeTernaryOperator(DartNode node, Type lhsType, Token operator,
DartNode diagnosticNode, DartExpression arg1, DartExpression arg2) {
String methodName = methodNameForBinaryOperator(operator);
HasSourceInfo problemTarget = getOperatorHasSourceInfo(node);
Member member = lookupMember(lhsType, methodName, problemTarget);
if (member != null) {
Element element = member.getElement();
node.setElement(element);
FunctionType methodType = getMethodType(lhsType, member, methodName, diagnosticNode);
checkDeprecated(problemTarget, element);
return checkInvocation(ImmutableList.of(arg1, arg2), diagnosticNode, methodName,
methodType, null);
} else {
return dynamicType;
}
}
@Override
public Type visitBinaryExpression(DartBinaryExpression node) {
DartExpression lhsNode = node.getArg1();
Type lhs = nonVoidTypeOf(lhsNode);
DartExpression rhsNode = node.getArg2();
Token operator = node.getOperator();
switch (operator) {
case ASSIGN: {
// prepare RHS type
Type rhs = getInvocationArgumentType(rhsNode);
try {
if (!hasInferredType(lhsNode)) {
if (checkAssignable(rhsNode, lhs, rhs)) {
inferFunctionLiteralParametersTypes(rhsNode, lhs);
}
}
} finally {
if (rhsNode instanceof DartFunctionExpression) {
rhsNode.accept(this);
}
}
// may be replace type of variable
setVariableElementType(lhsNode.getElement(), rhs, getTypeQuality(rhsNode));
checkAssignableElement(lhsNode);
// if cascade, then use type of "lhs" qualifier
if (lhsNode instanceof DartPropertyAccess) {
DartPropertyAccess lhsAccess = (DartPropertyAccess) lhsNode;
if (lhsAccess.isCascade()) {
return lhsAccess.getRealTarget().getType();
}
}
// use type or "rhs"
return rhs;
}
case ASSIGN_ADD: {
checkStringConcatPlus(node, lhs);
}
case ASSIGN_SUB:
case ASSIGN_MUL:
case ASSIGN_DIV:
case ASSIGN_MOD:
case ASSIGN_TRUNC: {
Token basicOperator = getBasicOperator(node, operator);
Type type = analyzeBinaryOperator(node, lhs, basicOperator, lhsNode, rhsNode);
checkAssignable(node, lhs, type);
checkAssignableElement(lhsNode);
return type;
}
case OR:
case AND: {
checkAssignable(lhsNode, boolType, lhs);
checkAssignable(boolType, rhsNode);
return boolType;
}
case ASSIGN_BIT_OR:
case ASSIGN_BIT_XOR:
case ASSIGN_BIT_AND:
case ASSIGN_SHL:
case ASSIGN_SAR: {
// Bit operations are only supported by integers and
// thus cannot be looked up on num. To ease usage of
// bit operations, we currently allow them to be used
// if the left-hand-side is of type num.
// TODO(karlklose) find a clean solution, i.e., without a special case for num.
checkAssignableElement(lhsNode);
if (lhs.equals(numType)) {
checkAssignable(rhsNode, numType, typeOf(rhsNode));
return intType;
} else {
Token basicOperator = getBasicOperator(node, operator);
Type type = analyzeBinaryOperator(node, lhs, basicOperator, lhsNode, rhsNode);
checkAssignable(node, lhs, type);
return type;
}
}
case BIT_OR:
case BIT_XOR:
case BIT_AND:
case SHL:
case SAR: {
// Bit operations are only supported by integers and
// thus cannot be looked up on num. To ease usage of
// bit operations, we currently allow them to be used
// if the left-hand-side is of type num.
// TODO(karlklose) find a clean solution, i.e., without a special case for num.
if (lhs.equals(numType)) {
checkAssignable(rhsNode, numType, typeOf(rhsNode));
return intType;
} else {
return analyzeBinaryOperator(node, lhs, operator, lhsNode, rhsNode);
}
}
case ADD: {
checkStringConcatPlus(node, lhs);
}
case SUB:
case MUL:
case DIV:
case TRUNC:
case MOD:
case LT:
case GT:
case LTE:
case GTE:
return analyzeBinaryOperator(node, lhs, operator, lhsNode, rhsNode);
case EQ: {
// try to resolve "==" to "operator equals()", but don't complain if can not find it
String methodName = methodNameForBinaryOperator(operator);
InterfaceType itype = types.getInterfaceType(lhs);
if (itype != null) {
Member member = itype.lookupMember(methodName);
if (member != null) {
node.setElement(member.getElement());
}
}
}
case NE:
case EQ_STRICT:
case NE_STRICT:
nonVoidTypeOf(rhsNode);
return boolType;
case AS:
return typeOf(rhsNode);
case IS:
if (rhsNode instanceof DartUnaryExpression) {
assert ((DartUnaryExpression) rhsNode).getOperator() == Token.NOT;
nonVoidTypeOf(((DartUnaryExpression) rhsNode).getArg());
} else {
nonVoidTypeOf(rhsNode);
}
return boolType;
case COMMA:
return typeOf(rhsNode);
default:
throw new AssertionError("Unknown operator: " + operator);
}
}
private void checkAssignableElement(DartExpression lhsNode) {
Element lhsElement = lhsNode.getElement();
switch (ElementKind.of(lhsElement)) {
case DYNAMIC:
case VARIABLE:
case PARAMETER:
case FIELD:
case NONE:
// OK or unknown
break;
case METHOD:
if (lhsElement.getModifiers().isSetter()
|| lhsElement.getModifiers().isGetter()
|| lhsElement.getModifiers().isOperator()) {
// The check for methods with setters is elsewhere.
break;
}
default:
onError(lhsNode, TypeErrorCode.CANNOT_ASSIGN_TO, ElementKind.of(lhsElement));
break;
}
}
private void checkStringConcatPlus(DartBinaryExpression binary, Type lhs) {
if (Objects.equal(lhs, stringType)) {
Token operator = binary.getOperator();
HasSourceInfo errorTarget = getOperatorHasSourceInfo(binary);
onError(errorTarget, TypeErrorCode.PLUS_CANNOT_BE_USED_FOR_STRING_CONCAT, operator);
}
}
/**
* @return the best guess for operator token location in the given {@link DartNode}.
*/
private static HasSourceInfo getOperatorHasSourceInfo(DartNode node) {
Token operator = null;
int offset = 0;
if (node instanceof DartBinaryExpression) {
DartBinaryExpression binary = (DartBinaryExpression) node;
operator = binary.getOperator();
offset = binary.getOperatorOffset();
}
if (node instanceof DartUnaryExpression) {
DartUnaryExpression binary = (DartUnaryExpression) node;
operator = binary.getOperator();
offset = binary.getOperatorOffset();
}
if (operator != null) {
Source source = node.getSourceInfo().getSource();
int length = operator.getSyntax().length();
final SourceInfo sourceInfo = new SourceInfo(source, offset, length);
return new HasSourceInfo() {
@Override
public SourceInfo getSourceInfo() {
return sourceInfo;
}
};
}
return node;
}
@Override
public Type visitExprStmt(DartExprStmt node) {
Type type = typeOf(node.getExpression());
return type;
}
@Override
public Type visitVariableStatement(DartVariableStatement node) {
Type type = typeOf(node.getTypeNode());
visit(node.getVariables());
return type;
}
private Member lookupMember(Type receiver, String methodName, HasSourceInfo problemTarget) {
InterfaceType itype = types.getInterfaceType(receiver);
if (itype == null) {
diagnoseNonInterfaceType(problemTarget, receiver);
return null;
}
Member member = itype.lookupMember(methodName);
if (member == null) {
member = itype.lookupMember("setter " + methodName);
}
// is "receiver" is inferred, attempt to find member in one of the subtypes
if (member == null) {
if (TypeQuality.of(receiver) == TypeQuality.INFERRED && receiver instanceof InterfaceType) {
member = ((InterfaceType) receiver).lookupSubTypeMember(methodName);
}
}
// report problem
if (member == null && problemTarget != null) {
if (reportNoMemberWhenHasInterceptor || !Elements.handlesNoSuchMethod(itype)) {
if (typeChecksForInferredTypes || !TypeQuality.isInferred(receiver)) {
ErrorCode code = TypeQuality.isInferred(receiver)
? TypeErrorCode.INTERFACE_HAS_NO_METHOD_NAMED_INFERRED
: TypeErrorCode.INTERFACE_HAS_NO_METHOD_NAMED;
typeError(problemTarget, code, receiver, methodName);
}
}
return null;
}
return member;
}
/**
* If left-hand-side is {@link VariableElement} with propagated type, then remember type before
* current "basic block" and set new type.
*/
private void setVariableElementType(Element element, Type type, TypeQuality quality) {
if (ElementKind.of(element) == ElementKind.VARIABLE) {
VariableElement variableElement = (VariableElement) element;
Type newType = Types.makeInferred(type, quality);
blockOldTypes.getFirst().setType(variableElement, newType);
}
}
/**
* @return <code>true</code> if given {@link DartNode} has inferred {@link Type}.
*/
private static boolean hasInferredType(DartNode node) {
return node != null && hasInferredType(node.getElement());
}
/**
* @return <code>true</code> if given {@link Element} is has inferred {@link Type}.
*/
private static boolean hasInferredType(Element element) {
return element != null && element.getType() != null
&& element.getType().getQuality() != TypeQuality.EXACT;
}
/**
* Helper for visiting {@link DartNode} which happens only if "condition" is satisfied. Attempts
* to infer types of {@link VariableElement}s from given "condition".
*/
private void visitConditionalNode(DartExpression condition, DartNode node) {
final VariableElementsRestorer variableRestorer = new VariableElementsRestorer();
try {
inferVariableTypesFromIsConditions(condition, variableRestorer);
typeOf(node);
} finally {
variableRestorer.restore();
}
}
/**
* Helper for setting {@link Type}s of {@link VariableElement}s when given "condition" is
* satisfied.
*/
private void inferVariableTypesFromIsConditions(DartExpression condition,
final VariableElementsRestorer variableRestorer) {
if (condition != null) {
condition.accept(new ASTVisitor<Void>() {
boolean negation = false;
@Override
public Void visitUnaryExpression(DartUnaryExpression node) {
boolean negationOld = negation;
try {
if (node.getOperator() == Token.NOT) {
negation = !negation;
}
return super.visitUnaryExpression(node);
} finally {
negation = negationOld;
}
}
@Override
public Void visitBinaryExpression(DartBinaryExpression node) {
// apply "as" always
// apply "is" only if not negated
if (node.getOperator() == Token.AS || node.getOperator() == Token.IS && !negation) {
DartExpression arg1 = node.getArg1();
DartExpression arg2 = node.getArg2();
if (arg1 instanceof DartIdentifier && arg1.getElement() instanceof VariableElement
&& arg2 instanceof DartTypeExpression) {
VariableElement variableElement = (VariableElement) arg1.getElement();
Type rhsType = arg2.getType();
Type varType = Types.makeInferred(rhsType);
variableRestorer.setType(variableElement, varType);
}
}
// operator || means that we can not be sure about types
if (node.getOperator() == Token.OR) {
return null;
}
// continue
return super.visitBinaryExpression(node);
}
});
}
}
/**
* Helper to temporarily set {@link Type} of {@link VariableElement} and restore original later.
*/
private class VariableElementsRestorer {
private final Map<VariableElement, Type> typesMap = Maps.newHashMap();
void setType(VariableElement element, Type inferredType) {
if (element == null) {
return;
}
Type currentType = element.getType();
// remember original if not yet
if (!typesMap.containsKey(element)) {
typesMap.put(element, currentType);
}
// apply inferred type
if (inferredType != null) {
if (TypeKind.of(currentType) == TypeKind.DYNAMIC && TypeQuality.isInferred(currentType)) {
// if we fell back to Dynamic, keep it
} else {
Type unionType = getUnionType(currentType, inferredType);
if (unionType != currentType) {
unionType = Types.makeInferred(unionType);
}
blockOldTypes.getFirst().rememberOldType(element, element.getType());
Elements.setType(element, unionType);
}
}
}
void restore() {
for (Entry<VariableElement, Type> entry : typesMap.entrySet()) {
Elements.setType(entry.getKey(), entry.getValue());
}
}
}
/**
* @return the {@link Type} which is both "a" and "b" types. May be "dynamic" if "a" and "b"
* don't form hierarchy.
*/
private Type getUnionType(Type curType, Type newType) {
if (TypeKind.of(curType) == TypeKind.DYNAMIC) {
return newType;
}
if (TypeKind.of(newType) == TypeKind.DYNAMIC) {
return curType;
}
if (types.isSubtype(curType, newType)) {
return curType;
}
if (types.isSubtype(newType, curType)) {
return newType;
}
// if InterfaceType, use union
if (curType instanceof InterfaceType && newType instanceof InterfaceType) {
return types.unionTypes(ImmutableList.of((InterfaceType) curType, (InterfaceType) newType));
}
// keep type as is
return curType;
}
/**
* @return <code>true</code> if we can prove that given {@link DartStatement} always leads to
* the exit from the enclosing function.
*/
private static boolean isExitFromFunction(DartStatement statement) {
return isExitFromFunction(statement, false);
}
/**
* @return <code>true</code> if we can prove that given {@link DartStatement} always leads to
* the exit from the enclosing function, or stops execution of the enclosing loop.
*/
private static boolean isExitFromFunctionOrLoop(DartStatement statement) {
return isExitFromFunction(statement, true);
}
/**
* @return <code>true</code> if we can prove that given {@link DartStatement} always leads to
* the exit from the enclosing function, or stops enclosing loop execution.
*/
private static boolean isExitFromFunction(DartStatement statement, boolean orLoop) {
// "return" is always exit
if (statement instanceof DartReturnStatement) {
return true;
}
// "throw" is exit if no enclosing "try"
if (statement instanceof DartExprStmt && (((DartExprStmt) statement).getExpression() instanceof DartThrowExpression)) {
for (DartNode p = statement; p != null && !(p instanceof DartFunction); p = p.getParent()) {
// TODO(scheglov) Can be enhanced:
// 1. check if there is "catch" block which can catch this exception;
// 2. even if there is such "catch", we will not visit the rest of the "try".
if (p instanceof DartTryStatement) {
return false;
}
}
return true;
}
// "block" is exit if its last statement is exit
if (statement instanceof DartBlock) {
DartBlock block = (DartBlock) statement;
List<DartStatement> statements = block.getStatements();
if (!statements.isEmpty()) {
return isExitFromFunction(statements.get(statements.size() - 1), orLoop);
}
}
// check also if we stop execution of the loop body
if (orLoop) {
if (statement instanceof DartContinueStatement) {
return true;
}
if (statement instanceof DartBreakStatement) {
return true;
}
}
// can not prove that given statement is always exit
return false;
}
/**
* Helper for setting {@link Type}s of {@link VariableElement}s when given "condition" is NOT
* satisfied.
*/
private static void inferVariableTypesFromIsNotConditions(DartExpression condition,
final VariableElementsRestorer variableRestorer) {
condition.accept(new ASTVisitor<Void>() {
boolean negation = false;
@Override
public Void visitUnaryExpression(DartUnaryExpression node) {
boolean negationOld = negation;
try {
if (node.getOperator() == Token.NOT) {
negation = !negation;
}
return super.visitUnaryExpression(node);
} finally {
negation = negationOld;
}
}
@Override
public Void visitBinaryExpression(DartBinaryExpression node) {
// analyze (v is Type)
if (node.getOperator() == Token.IS) {
DartExpression arg1 = node.getArg1();
DartExpression arg2 = node.getArg2();
if (arg1 instanceof DartIdentifier && arg1.getElement() instanceof VariableElement) {
VariableElement variableElement = (VariableElement) arg1.getElement();
// !(v is Type)
if (negation && arg2 instanceof DartTypeExpression) {
Type isType = arg2.getType();
Type varType = Types.makeInferred(isType);
variableRestorer.setType(variableElement, varType);
}
// (v is! Type)
if (!negation) {
if (arg2 instanceof DartUnaryExpression) {
DartUnaryExpression unary2 = (DartUnaryExpression) arg2;
if (unary2.getOperator() == Token.NOT
&& unary2.getArg() instanceof DartTypeExpression) {
Type isType = unary2.getArg().getType();
Type varType = Types.makeInferred(isType);
variableRestorer.setType(variableElement, varType);
}
}
}
}
}
// visit || expressions
if (node.getOperator() == Token.OR) {
return super.visitBinaryExpression(node);
}
// other operators, such as && - don't infer types
return null;
}
});
}
/**
* If type of variable-like {@link DartDeclaration} (i.e. variables, parameter, field) is not
* specified and we know somehow this type, then use it.
*/
private static void inferVariableDeclarationType(DartDeclaration<?> node, DartExpression value) {
Type type = value.getType();
TypeQuality quality = getTypeQuality(value);
inferVariableDeclarationType(node, type, quality);
}
/**
* If type of variable-like {@link DartDeclaration} (i.e. variables, parameter, field) is not
* specified and we know somehow this type, then use it.
*/
private static void inferVariableDeclarationType(DartDeclaration<?> node, Type type,
TypeQuality typeQuality) {
if (type != null && TypeKind.of(type) != TypeKind.DYNAMIC) {
Element element = node.getElement();
if (element != null && TypeKind.of(element.getType()) == TypeKind.DYNAMIC) {
Type inferredType = Types.makeInferred(type, typeQuality);
Elements.setType(element, inferredType);
node.getName().setType(inferredType);
}
}
}
/**
* If given "mayBeLiteral" is {@link DartFunctionExpression} without explicit parameters types
* and its required type is {@link FunctionAliasType}, then infer parameters types from
* {@link FunctionAliasType}.
*/
private static void inferFunctionLiteralParametersTypes(DartExpression mayBeLiteral,
Type mayBeFunctionType) {
if (mayBeLiteral instanceof DartFunctionExpression) {
// prepare required type of function literal
FunctionType requiredType = null;
if (TypeKind.of(mayBeFunctionType) == TypeKind.FUNCTION) {
requiredType = (FunctionType) mayBeFunctionType;
}
if (TypeKind.of(mayBeFunctionType) == TypeKind.FUNCTION_ALIAS) {
FunctionAliasType functionAliasType = (FunctionAliasType) mayBeFunctionType;
requiredType = Types.asFunctionType(functionAliasType);
}
// OK, we can try to infer parameter types
if (requiredType != null) {
DartFunctionExpression literal = (DartFunctionExpression) mayBeLiteral;
List<DartParameter> parameterNodes = literal.getFunction().getParameters();
// try to infer types of "normal" parameters
List<Type> requiredNormalParameterTypes = requiredType.getParameterTypes();
int n = Math.min(requiredNormalParameterTypes.size(), parameterNodes.size());
for (int i = 0; i < n; i++) {
Type requiredNormalParameterType = requiredNormalParameterTypes.get(i);
DartParameter parameterNode = parameterNodes.get(i);
inferVariableDeclarationType(parameterNode, requiredNormalParameterType,
TypeQuality.INFERRED);
}
}
}
}
/**
* When we cannot prove that node was visited, then type is intersection of old/new types.
*/
private void setMergedVariableTypes(BlockTypeContext blockTypeContext) {
for (VariableElement variable : blockTypeContext.newTypes.keySet()) {
Type newType = blockTypeContext.newTypes.get(variable);
Type oldType = blockTypeContext.oldTypes.get(variable);
Type mergedType = types.intersection(newType, oldType);
TypeQuality mergedTypeQuality = Types.getIntersectionQuality(newType, oldType);
setVariableElementType(variable, mergedType, mergedTypeQuality);
}
}
private boolean checkAssignable(DartNode node, Type t, Type s) {
t.getClass(); // Null check.
s.getClass(); // Null check.
// ignore inferred types, treat them as Dynamic
if (!typeChecksForInferredTypes) {
if (TypeQuality.isInferred(t) || TypeQuality.isInferred(s)) {
return true;
}
}
// do check and report error
if (!types.isAssignable(t, s)) {
TypeErrorCode errorCode = TypeQuality.isInferred(t) || TypeQuality.isInferred(s)
? TypeErrorCode.TYPE_NOT_ASSIGNMENT_COMPATIBLE_INFERRED
: TypeErrorCode.TYPE_NOT_ASSIGNMENT_COMPATIBLE;
typeError(node, errorCode, s, t);
return false;
}
// OK
return true;
}
private boolean checkAssignable(Type targetType, DartExpression node) {
// analyze "node"
Type nodeType = typeOf(node);
// target is Dynamic, any source type is good, even "void"
if (TypeKind.of(targetType) == TypeKind.DYNAMIC) {
return true;
}
// source was Dynamic
if (hasInferredType(node)) {
return true;
}
// OK, check types
checkNonVoid(node, nodeType);
return checkAssignable(node, targetType, nodeType);
}
private FunctionType getMethodType(Type receiver, Member member, String name,
DartNode diagnosticNode) {
FunctionType functionType = getMethodType0(receiver, member, name, diagnosticNode);
if (TypeQuality.isInferred(receiver)) {
functionType = (FunctionType) Types.makeInferred(functionType);
}
return functionType;
}
private FunctionType getMethodType0(Type receiver, Member member, String name,
DartNode diagnosticNode) {
if (member == null) {
return dynamicType;
}
Element element = member.getElement();
switch (ElementKind.of(element)) {
case METHOD: {
MethodElement method = (MethodElement) element;
if (method.getModifiers().isStatic()) {
return typeError(diagnosticNode, TypeErrorCode.IS_STATIC_METHOD_IN,
name, receiver);
}
return (FunctionType) member.getType();
}
case FIELD: {
FieldElement field = (FieldElement) element;
if (field.getModifiers().isStatic()) {
return typeError(diagnosticNode, TypeErrorCode.IS_STATIC_FIELD_IN,
name, receiver);
}
switch (TypeKind.of(member.getType())) {
case FUNCTION:
return (FunctionType) member.getType();
case FUNCTION_ALIAS:
return Types.asFunctionType((FunctionAliasType) member.getType());
default:
// target.field() as Function invocation.
if (Elements.isFieldWithGetter(field)) {
Type fieldType = field.getType();
if (!types.isAssignable(functionType, fieldType)) {
onError(diagnosticNode, TypeErrorCode.NOT_A_FUNCTION_TYPE_FIELD, field.getName(), fieldType);
}
}
return dynamicType;
}
}
default:
if (typeChecksForInferredTypes || !TypeQuality.isInferred(receiver)) {
TypeErrorCode errorCode = TypeQuality.isInferred(receiver)
? TypeErrorCode.NOT_A_METHOD_IN_INFERRED : TypeErrorCode.NOT_A_METHOD_IN;
typeError(diagnosticNode, errorCode, name, receiver);
}
return dynamicType;
}
}
private Type diagnoseNonInterfaceType(HasSourceInfo node, Type type) {
switch (TypeKind.of(type)) {
case DYNAMIC:
return type;
case FUNCTION:
case FUNCTION_ALIAS:
case INTERFACE:
case VARIABLE:
// Cannot happen.
throw internalError(node, type.toString());
case NONE:
throw internalError(node, "type is null");
case VOID:
return typeError(node, TypeErrorCode.VOID);
default:
throw internalError(node, type.getKind().name());
}
}
private Type checkArguments(DartNode diagnosticNode,
List<DartExpression> argumentNodes,
Iterator<Type> argumentTypes, FunctionType ftype,
List<VariableElement> parameters) {
int argumentIndex = 0;
// Check positional parameters.
{
List<Type> parameterTypes = ftype.getParameterTypes();
for (Type parameterType : parameterTypes) {
parameterType.getClass(); // quick null check
if (argumentTypes.hasNext()) {
Type argumentType = argumentTypes.next();
argumentType.getClass(); // quick null check
DartExpression argumentNode = argumentNodes.get(argumentIndex);
if (argumentNode instanceof DartNamedExpression) {
onError(argumentNode, TypeErrorCode.EXPECTED_POSITIONAL_ARGUMENT, parameterType);
return ftype.getReturnType();
}
if (parameters != null) {
argumentNode.setInvocationParameterId(parameters.get(argumentIndex));
} else {
argumentNode.setInvocationParameterId(argumentIndex);
}
if (checkAssignable(argumentNode, parameterType, argumentType)) {
inferFunctionLiteralParametersTypes(argumentNode, parameterType);
}
argumentIndex++;
} else {
onError(diagnosticNode, TypeErrorCode.MISSING_ARGUMENT, parameterType);
return ftype.getReturnType();
}
}
}
// Check optional parameters.
{
Map<String, Type> optionalParameterTypes = ftype.getOptionalParameterTypes();
Iterator<Entry<String, Type>> optionalParameterTypesIterator =
optionalParameterTypes.entrySet().iterator();
while (optionalParameterTypesIterator.hasNext()
&& argumentTypes.hasNext()
&& !(argumentNodes.get(argumentIndex) instanceof DartNamedExpression)) {
Entry<String, Type> namedEntry = optionalParameterTypesIterator.next();
Type optionalType = namedEntry.getValue();
optionalType.getClass(); // quick null check
Type argumentType = argumentTypes.next();
argumentType.getClass(); // quick null check
DartExpression argumentNode = argumentNodes.get(argumentIndex);
if (parameters != null) {
argumentNode.setInvocationParameterId(parameters.get(argumentIndex));
} else {
argumentNode.setInvocationParameterId(argumentIndex);
}
if (checkAssignable(argumentNode, optionalType, argumentType)) {
inferFunctionLiteralParametersTypes(argumentNode, optionalType);
}
argumentIndex++;
}
}
// Check named parameters.
{
Set<String> usedNamedParametersPositional = Sets.newHashSet();
Set<String> usedNamedParametersNamed = Sets.newHashSet();
Map<String, Type> namedParameterTypes = ftype.getNamedParameterTypes();
while (argumentTypes.hasNext()
&& argumentNodes.get(argumentIndex) instanceof DartNamedExpression) {
DartNamedExpression namedExpression =
(DartNamedExpression) argumentNodes.get(argumentIndex);
DartExpression argumentNode = namedExpression.getExpression();
// Prepare parameter name.
String parameterName = namedExpression.getName().getName();
if (parameters != null) {
for (VariableElement parameter : parameters) {
if (Objects.equal(parameter.getName(), parameterName)) {
namedExpression.setInvocationParameterId(parameter);
namedExpression.getName().setInvocationParameterId(parameter);
argumentNode.setInvocationParameterId(parameter);
break;
}
}
} else {
namedExpression.setInvocationParameterId(parameterName);
argumentNode.setInvocationParameterId(parameterName);
}
if (usedNamedParametersPositional.contains(parameterName)) {
onError(namedExpression, TypeErrorCode.DUPLICATE_NAMED_ARGUMENT);
} else if (usedNamedParametersNamed.contains(parameterName)) {
onError(namedExpression, ResolverErrorCode.DUPLICATE_NAMED_ARGUMENT);
} else {
usedNamedParametersNamed.add(parameterName);
}
// Check parameter type.
Type namedParameterType = namedParameterTypes.get(parameterName);
Type argumentType = argumentTypes.next();
if (namedParameterType != null) {
argumentType.getClass(); // quick null check
if (checkAssignable(argumentNode, namedParameterType, argumentType)) {
inferFunctionLiteralParametersTypes(argumentNode, namedParameterType);
}
} else {
onError(namedExpression, TypeErrorCode.NO_SUCH_NAMED_PARAMETER, parameterName);
}
argumentIndex++;
}
}
// Check rest (currently removed from specification).
if (ftype.hasRest()) {
while (argumentTypes.hasNext()) {
checkAssignable(argumentNodes.get(argumentIndex), ftype.getRest(), argumentTypes.next());
argumentIndex++;
}
}
// Report extra arguments.
while (argumentTypes.hasNext()) {
argumentTypes.next();
onError(argumentNodes.get(argumentIndex), TypeErrorCode.EXTRA_ARGUMENT);
argumentIndex++;
}
// Return type.
Type type = ftype.getReturnType();
type.getClass(); // quick null check
return type;
}
@Override
public Type visitTypeNode(DartTypeNode node) {
return validateTypeNode(node);
}
private Type validateTypeNode(DartTypeNode node) {
Type type = node.getType(); // Already calculated by resolver.
switch (TypeKind.of(type)) {
case NONE:
return typeError(node, TypeErrorCode.INTERNAL_ERROR,
String.format("type \"%s\" is null", node));
case INTERFACE: {
InterfaceType itype = (InterfaceType) type;
validateBounds(node.getTypeArguments(),
itype.getArguments(),
itype.getElement().getTypeParameters());
return itype;
}
default:
return type;
}
}
private void validateBounds(List<? extends DartNode> diagnosticNodes,
List<Type> arguments,
List<Type> parameters) {
if (arguments.size() == parameters.size() && arguments.size() == diagnosticNodes.size()) {
List<Type> bounds = Lists.newArrayListWithCapacity(parameters.size());
for (Type parameter : parameters) {
TypeVariable variable = (TypeVariable) parameter;
Type bound = variable.getTypeVariableElement().getBound();
if (bound == null) {
internalError(variable.getElement(), "bound is null");
}
bounds.add(bound);
}
bounds = Types.subst(bounds, arguments, parameters);
for (int i = 0; i < arguments.size(); i++) {
Type t = bounds.get(i);
Type s = arguments.get(i);
if (!types.isAssignable(t, s)) {
onError(diagnosticNodes.get(i),
TypeErrorCode.TYPE_NOT_ASSIGNMENT_COMPATIBLE, s, t);
}
}
}
}
/* Check for a type variable is repeated in its own bounds:
* e.g. Foo<T extends T>
*/
private void checkCyclicBounds(List<? extends Type> arguments) {
for (Type argument : arguments) {
if (TypeKind.of(argument).equals(TypeKind.VARIABLE)) {
TypeVariable typeVar = (TypeVariable) argument;
checkCyclicBound(typeVar, typeVar.getTypeVariableElement().getBound());
}
}
}
private void checkCyclicBound(TypeVariable variable, Type bound) {
switch(TypeKind.of(bound)) {
case VARIABLE: {
TypeVariable boundType = (TypeVariable)bound;
if (boundType.equals(variable)) {
onError(boundType.getElement(),
TypeErrorCode.CYCLIC_REFERENCE_TO_TYPE_VARIABLE,
boundType.getElement().getOriginalName());
}
break;
}
default:
break;
}
}
/**
* Returns the type of a node. If a type of an expression can't be resolved,
* returns the dynamic type.
*
* @return a non-null type
*/
Type typeOf(DartNode node) {
if (node == null) {
return dynamicType;
}
// prepare new type
Type result = node.accept(this);
if (result == null) {
return dynamicType;
}
// set new type, or keep existing
if (node.getType() == null) {
node.setType(result);
} else {
result = node.getType();
}
// done
return result;
}
/**
* Returns the type of a node, registering an error if the type is unresolved or
* void.
*
* @return a non-null type
*/
private Type nonVoidTypeOf(DartNode node) {
Type type = typeOf(node);
return checkNonVoid(node, type);
}
/**
* @return the given {@link Type}, registering an error if it is unresolved or void.
*/
private Type checkNonVoid(HasSourceInfo errorTarget, Type type) {
switch (TypeKind.of(type)) {
case VOID:
case NONE:
return typeError(errorTarget, TypeErrorCode.VOID);
default:
return type;
}
}
@Override
public Type visitArrayAccess(DartArrayAccess node) {
Type target = typeOf(node.getRealTarget());
DartExpression argKey = node.getKey();
// t[k] = v
if (node.getParent() instanceof DartBinaryExpression) {
DartBinaryExpression binary = (DartBinaryExpression) node.getParent();
if (binary.getArg1() == node && binary.getOperator() == Token.ASSIGN) {
DartExpression argValue = binary.getArg2();
analyzeTernaryOperator(node, target, Token.ASSIGN_INDEX, node, argKey, argValue);
binary.setElement(node.getElement());
return argValue.getType();
}
}
// print( t[k] )
Type result = analyzeBinaryOperator(node, target, Token.INDEX, node, argKey);
return Types.makeInferred(result, target.getQuality());
}
/**
* Asserts that given {@link DartExpression} is valid for using in "assert" statement.
*/
private void checkAssertCondition(DartExpression conditionNode) {
Type condition = nonVoidTypeOf(conditionNode);
switch (condition.getKind()) {
case FUNCTION:
FunctionType ftype = (FunctionType) condition;
Type returnType = ftype.getReturnType();
if (!types.isAssignable(boolType, returnType) || !ftype.getParameterTypes().isEmpty()) {
typeError(conditionNode, TypeErrorCode.ASSERT_BOOL);
}
break;
default:
if (!types.isAssignable(boolType, condition)) {
typeError(conditionNode, TypeErrorCode.ASSERT_BOOL);
}
break;
}
}
@Override
public Type visitBlock(DartBlock node) {
try {
return typeAsVoid(node);
} finally {
VariableElementsRestorer variableRestorer = restoreOnBlockExit.remove(node);
if (variableRestorer != null) {
variableRestorer.restore();
}
}
}
private Type typeAsVoid(DartNode node) {
node.visitChildren(this);
return voidType;
}
@Override
public Type visitBreakStatement(DartBreakStatement node) {
return voidType;
}
@Override
public Type visitCascadeExpression(DartCascadeExpression node) {
DartExpression target = node.getTarget();
Type type = nonVoidTypeOf(target);
node.setType(type);
inferCascadeType(node);
node.visitChildren(this);
return type;
}
/**
* Infers {@link Type} of {@link DartCascadeExpression} from context.
*/
private void inferCascadeType(DartCascadeExpression node) {
// field declaration
if (node.getParent() instanceof DartField) {
DartField field = (DartField) node.getParent();
Type varType = field.getElement().getType();
setCascadeUnionType(node, varType);
}
// variable declaration
if (node.getParent() instanceof DartVariable) {
DartVariable var = (DartVariable) node.getParent();
Type varType = var.getElement().getType();
setCascadeUnionType(node, varType);
}
// assignment
if (node.getParent() instanceof DartBinaryExpression) {
DartBinaryExpression binary = (DartBinaryExpression) node.getParent();
if (binary.getOperator() == Token.ASSIGN && binary.getArg2() == node
&& binary.getArg1() != null) {
Element leftElement = binary.getArg1().getElement();
if (leftElement != null) {
Type varType = leftElement.getType();
setCascadeUnionType(node, varType);
}
}
}
}
/**
* Sets for given {@link DartCascadeExpression} and its target {@link Type} which is union of
* existing type and "newType".
*/
private void setCascadeUnionType(DartCascadeExpression node, Type newType) {
DartExpression target = node.getTarget();
Type type = node.getType();
if (isExplicitlySpecifiedType(newType) && types.isAssignable(type, newType)) {
Type unionType = getUnionType(type, newType);
unionType = Types.makeInferred(unionType);
node.setType(unionType);
target.setType(unionType);
}
}
@Override
public Type visitFunctionObjectInvocation(DartFunctionObjectInvocation node) {
ClassElement element = functionType.getElement();
node.setElement(element);
checkDeprecated(node, element);
return checkInvocation(node, node, null, typeOf(node.getTarget()));
}
@Override
public Type visitMethodInvocation(DartMethodInvocation node) {
if (node.getFunctionName().isResolutionAlreadyReportedThatTheMethodCouldNotBeFound()) {
return dynamicType;
}
DartNode target = node.getRealTarget();
DartIdentifier nameNode = node.getFunctionName();
String name = node.getFunctionNameString();
Element element = node.getElement();
if (element != null && (element.getModifiers().isStatic()
|| Elements.isTopLevel(element))) {
typeOf(target);
node.setElement(element);
checkDeprecated(nameNode, element);
return checkInvocation(node, nameNode, name, element.getType());
}
Type receiver = nonVoidTypeOf(target);
Member member = lookupMember(receiver, name, nameNode);
if (member != null) {
element = member.getElement();
checkIllegalPrivateAccess(node.getFunctionName(), element, name);
node.setElement(element);
if (nameNode != null) {
nameNode.setElement(element);
}
}
if (Elements.isAbstractFieldWithoutGetter(element)) {
onError(nameNode, TypeErrorCode.USE_ASSIGNMENT_ON_SETTER, name);
}
checkDeprecated(nameNode, nameNode.getElement());
FunctionType methodType = getMethodType(receiver, member, name, nameNode);
Type returnType = checkInvocation(node, nameNode, name, methodType);
returnType = ExternalTypeAnalyzers.resolve(types, node, element, returnType);
warningEffectiveIntegerDivision(node, element);
return returnType;
}
/**
* http://code.google.com/p/dart/issues/detail?id=5652
*/
private void warningEffectiveIntegerDivision(DartMethodInvocation node, Element element) {
if (element != null && element.getName().equals("toInt")
&& element.getEnclosingElement() != null
&& element.getEnclosingElement().getName().equals("num")) {
DartExpression target = node.getTarget();
while (target instanceof DartParenthesizedExpression) {
target = ((DartParenthesizedExpression) target).getExpression();
}
if (target instanceof DartBinaryExpression) {
DartBinaryExpression binary = (DartBinaryExpression) target;
if (binary.getOperator() == Token.DIV && intType.equals(binary.getArg1().getType())
&& intType.equals(binary.getArg2().getType())) {
typeError(node, TypeErrorCode.USE_INTEGER_DIVISION);
}
}
}
}
private void checkIllegalPrivateAccess(DartNode diagnosticNode, Element element, String name) {
if (DartIdentifier.isPrivateName(name)) {
if (!Elements.areSameLibrary(currentMethod, element)) {
onError(diagnosticNode, TypeErrorCode.ILLEGAL_ACCESS_TO_PRIVATE, name);
}
}
}
@Override
public Type visitSuperConstructorInvocation(DartSuperConstructorInvocation node) {
return checkConstructorForwarding(node, node.getElement());
}
private Type checkConstructorForwarding(DartInvocation node, ConstructorElement element) {
if (element == null) {
visit(node.getArguments());
return voidType;
} else {
node.setElement(element);
checkDeprecated(node, element);
checkInvocation(node, node, null, typeAsMemberOf(element, currentClass));
return voidType;
}
}
@Override
public Type visitCase(DartCase node) {
node.visitChildren(this);
return voidType;
}
@Override
public Type visitClass(DartClass node) {
ClassNodeElement element = node.getElement();
InterfaceType type = element.getType();
checkCyclicBounds(type.getArguments());
// remember unimplemented members
{
List<Element> unimplementedMembers = findUnimplementedMembers(element);
if (!node.getModifiers().isAbstract() && !unimplementedMembers.isEmpty()) {
StringBuilder sb = getUnimplementedMembersMessage(element, unimplementedMembers);
onError(node.getName(), TypeErrorCode.CONTRETE_CLASS_WITH_UNIMPLEMENTED_MEMBERS,
node.getName(), sb.toString());
}
}
//
setCurrentClass(type);
visit(node.getTypeParameters());
if (node.getSuperclass() != null) {
validateTypeNode(node.getSuperclass());
}
if (node.getInterfaces() != null) {
for (DartTypeNode interfaceNode : node.getInterfaces()) {
validateTypeNode(interfaceNode);
}
}
visit(node.getMembers());
checkInterfaceConstructors(element);
try {
checkClassDuplicateInterfaces(node, element, element.getAllSupertypes());
} catch (CyclicDeclarationException ignored) {
}
// Finish current class.
setCurrentClass(null);
return type;
}
/**
* Check for duplicate interfaces that aren't assignable to each other due to parameterization.
*
* Issue 3803: This isn't in the spec as of 0.10, adding as a 'value added' check, because there is no
* way to satisfy the method override rules without either causing errors in checked mode or
* causing override errors for every method implemented for this interface.
*
* @param classElement
*/
private void checkClassDuplicateInterfaces(DartClass node, ClassElement classElement,
List<InterfaceType> allSupertypes) {
Map<Element, InterfaceType> elementMap = Maps.newHashMap();
for (InterfaceType supertype : allSupertypes) {
Element e = supertype.getElement();
if (e != null) {
InterfaceType foundType = elementMap.get(e);
if (foundType != null && ! types.isAssignable(supertype, foundType)) {
typeError(node.getName(), TypeErrorCode.INCOMPATIBLE_TYPES_IN_HIERARCHY, foundType.toString(),
supertype.toString());
} else {
elementMap.put(e, supertype);
}
}
}
}
/**
* Checks that interface constructors have corresponding methods in default class.
*/
private void checkInterfaceConstructors(ClassElement interfaceElement) {
// If no default class, do nothing.
if (interfaceElement.getDefaultClass() == null) {
return;
}
// Analyze all constructors.
String interfaceClassName = interfaceElement.getName();
String defaultClassName = interfaceElement.getDefaultClass().getElement().getName();
for (ConstructorElement interfaceConstructor : interfaceElement.getConstructors()) {
ConstructorElement defaultConstructor = interfaceConstructor.getDefaultConstructor();
if (defaultConstructor != null) {
// TODO(scheglov)
// It is a compile-time error if kI and kF do not have identical type parameters
// TODO /end
// Validate types of required and optional parameters.
{
List<String> interfaceTypes = Elements.getParameterTypeNames(interfaceConstructor);
List<String> defaultTypes = Elements.getParameterTypeNames(defaultConstructor);
if (interfaceTypes.size() == defaultTypes.size()
&& !interfaceTypes.equals(defaultTypes)) {
onError(
interfaceConstructor,
TypeErrorCode.DEFAULT_CONSTRUCTOR_TYPES,
Elements.getRawMethodName(interfaceConstructor),
interfaceClassName,
Joiner.on(",").join(interfaceTypes),
Elements.getRawMethodName(defaultConstructor),
defaultClassName,
Joiner.on(",").join(defaultTypes));
}
}
}
}
}
private List<Element> findUnimplementedMembers(ClassElement classElement) {
// May be has members already (cached or already analyzed ClassNodeElement).
List<Element> members = classElement.getUnimplementedMembers();
if (members != null) {
return members;
}
// If no cached result, then should be node based.
ClassNodeElement classNodeElement = (ClassNodeElement) classElement;
// Analyze ClassElement node.
AbstractMethodFinder finder = new AbstractMethodFinder(classNodeElement.getType());
classNodeElement.getNode().accept(finder);
// Prepare unimplemented members.
if (classNodeElement.isInterface()) {
members = Collections.emptyList();
} else {
members = finder.unimplementedElements;
}
// Remember unimplemented methods.
classNodeElement.setUnimplementedMembers(members);
return members;
}
@Override
public Type visitConditional(DartConditional node) {
checkCondition(node.getCondition());
Type left = typeOf(node.getThenExpression());
Type right = typeOf(node.getElseExpression());
return types.intersection(left, right);
}
private Type checkCondition(DartExpression condition) {
Type type = nonVoidTypeOf(condition);
checkAssignable(condition, boolType, type);
return type;
}
@Override
public Type visitContinueStatement(DartContinueStatement node) {
return voidType;
}
@Override
public Type visitDefault(DartDefault node) {
node.visitChildren(this);
return typeAsVoid(node);
}
@Override
public Type visitDoWhileStatement(DartDoWhileStatement node) {
checkCondition(node.getCondition());
typeOf(node.getBody());
return voidType;
}
@Override
public Type visitEmptyStatement(DartEmptyStatement node) {
return typeAsVoid(node);
}
@Override
public Type visitFieldDefinition(DartFieldDefinition node) {
node.visitChildren(this);
return voidType;
}
@Override
public Type visitForInStatement(DartForInStatement node) {
Type variableType;
VariableElement variableElement = null;
if (node.introducesVariable()) {
variableType = typeOf(node.getVariableStatement());
variableElement = node.getVariableStatement().getVariables().get(0).getElement();
} else {
variableType = typeOf(node.getIdentifier());
// in most cases variable, but sometimes field
NodeElement identifierElement = node.getIdentifier().getElement();
if (identifierElement instanceof VariableElement) {
variableElement = (VariableElement) identifierElement;
}
}
// prepare Iterable type
DartExpression iterableExpression = node.getIterable();
Type iterableType = typeOf(iterableExpression);
// analyze compatibility of variable and Iterator elements types
Member iteratorMember = lookupMember(iterableType, "iterator", iterableExpression);
Type elementType = null;
if (iteratorMember != null) {
if (TypeKind.of(iteratorMember.getType()) == TypeKind.FUNCTION) {
FunctionType iteratorMethod = (FunctionType) iteratorMember.getType();
InterfaceType asInstanceOf = types.asInstanceOf(iteratorMethod.getReturnType(),
dynamicIteratorType.getElement());
if (asInstanceOf != null) {
elementType = asInstanceOf.getArguments().get(0);
checkAssignable(iterableExpression, variableType, elementType);
} else {
InterfaceType expectedIteratorType = dynamicIteratorType.subst(
Arrays.asList(variableType), dynamicIteratorType.getElement().getTypeParameters());
typeError(iterableExpression,
TypeErrorCode.FOR_IN_WITH_INVALID_ITERATOR_RETURN_TYPE,
expectedIteratorType);
}
} else {
// Not a function
typeError(iterableExpression, TypeErrorCode.FOR_IN_WITH_ITERATOR_FIELD);
}
}
// visit body with inferred variable type
VariableElementsRestorer variableRestorer = new VariableElementsRestorer();
try {
if (variableElement != null && elementType != null) {
variableRestorer.setType(variableElement, elementType);
}
BlockTypeContext blockTypeContext = new BlockTypeContext();
blockOldTypes.addFirst(blockTypeContext);
try {
return typeAsVoid(node.getBody());
} finally {
blockOldTypes.removeFirst();
setMergedVariableTypes(blockTypeContext);
}
} finally {
variableRestorer.restore();
}
}
@Override
public Type visitForStatement(DartForStatement node) {
typeOf(node.getInit());
DartExpression condition = node.getCondition();
checkCondition(condition);
// visit body
BlockTypeContext blockTypeContext = new BlockTypeContext();
blockOldTypes.addFirst(blockTypeContext);
try {
visitConditionalNode(condition, node.getBody());
visitConditionalNode(condition, node.getIncrement());
} finally {
blockOldTypes.removeFirst();
setMergedVariableTypes(blockTypeContext);
}
// done
return voidType;
}
@Override
public Type visitFunction(DartFunction node) {
blockOldTypes.addFirst(new BlockTypeContext());
try {
Type previous = expected;
visit(node.getParameters());
expected = typeOf(node.getReturnTypeNode());
typeOf(node.getBody());
expected = previous;
} finally {
blockOldTypes.removeFirst();
}
return voidType;
}
@Override
public Type visitFunctionExpression(DartFunctionExpression node) {
node.visitChildren(this);
Type result = ((Element) node.getElement()).getType();
result.getClass(); // quick null check
return result;
}
@Override
public Type visitFunctionTypeAlias(DartFunctionTypeAlias node) {
FunctionAliasElement element = node.getElement();
FunctionAliasType type = element.getType();
if (TypeKind.of(type) == TypeKind.FUNCTION_ALIAS) {
checkCyclicBounds(type.getElement().getTypeParameters());
if (hasFunctionTypeAliasSelfReference(element)) {
onError(node, TypeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF);
}
}
return typeAsVoid(node);
}
@Override
public Type visitSyntheticErrorIdentifier(DartSyntheticErrorIdentifier node) {
return dynamicType;
}
@Override
public Type visitIdentifier(DartIdentifier node) {
if (node.getType() != null) {
return node.getType();
}
if (node.getParent() instanceof DartDeclaration<?>
&& ((DartDeclaration<?>) node.getParent()).getName() == node) {
return node.getType();
}
if (node.getParent() instanceof DartComment) {
return dynamicType;
}
Element element = node.getElement();
Type type;
switch (ElementKind.of(element)) {
case VARIABLE:
case PARAMETER:
case FUNCTION_OBJECT:
type = element.getType();
type.getClass(); // quick null check
break;
case CLASS:
return element.getType();
case FIELD:
type = typeAsMemberOf(element, currentClass);
// try to resolve as getter/setter
FieldElement fieldElement = (FieldElement) element;
DartNode properyAccess = ASTNodes.getPropertyAccessNode(node);
if (ASTNodes.inGetterContext(properyAccess)) {
MethodElement getter = fieldElement.getGetter();
if (getter != null) {
type = ((FunctionType) typeAsMemberOf(getter, currentClass)).getReturnType();
node.setType(type);
}
} else if (ASTNodes.inSetterContext(properyAccess)) {
MethodElement setter = fieldElement.getSetter();
if (setter != null) {
if (setter.getParameters().size() > 0) {
type = setter.getParameters().get(0).getType();
node.setType(type);
}
}
}
type.getClass(); // quick null check
break;
case METHOD:
type = typeAsMemberOf(element, currentClass);
type.getClass(); // quick null check
break;
case NONE:
if (!node.isResolutionAlreadyReportedThatTheMethodCouldNotBeFound()) {
typeError(node, TypeErrorCode.CANNOT_BE_RESOLVED, node.getName());
}
return dynamicType;
case DYNAMIC:
return element.getType();
default:
return voidType;
}
return type;
}
@Override
public Type visitIfStatement(DartIfStatement node) {
DartExpression condition = node.getCondition();
checkCondition(condition);
// visit "then"
BlockTypeContext thenTypeContext = new BlockTypeContext();
blockOldTypes.addFirst(thenTypeContext);
DartStatement thenStatement = node.getThenStatement();
visitConditionalNode(condition, thenStatement);
blockOldTypes.removeFirst();
Map<VariableElement, Type> thenVariableTypes = thenTypeContext.getNewTypesAndRestoreOld();
// visit "else"
DartStatement elseStatement = node.getElseStatement();
BlockTypeContext elseTypeContext = new BlockTypeContext();
{
VariableElementsRestorer variableRestorer = new VariableElementsRestorer();
// if has "else", then types inferred from "is! Type" applied only to "else"
if (elseStatement != null) {
blockOldTypes.addFirst(elseTypeContext);
inferVariableTypesFromIsNotConditions(condition, variableRestorer);
typeOf(elseStatement);
variableRestorer.restore();
blockOldTypes.removeFirst();
}
// if no "else", then inferred types applied to the end of the method/loop
if (elseStatement == null) {
if (isExitFromFunction(thenStatement)) {
inferVariableTypesFromIsNotConditions(condition, variableRestorer);
} else if (isExitFromFunctionOrLoop(thenStatement)) {
DartBlock restoreBlock = getBlockForLoopTypesInference(node);
variableRestorer = restoreOnBlockExit.get(restoreBlock);
if (variableRestorer == null) {
variableRestorer = new VariableElementsRestorer();
restoreOnBlockExit.put(restoreBlock, variableRestorer);
}
restoreOnBlockExit.put(restoreBlock, variableRestorer);
inferVariableTypesFromIsNotConditions(condition, variableRestorer);
}
}
}
Map<VariableElement, Type> elseVariableTypes = elseTypeContext.getNewTypesAndRestoreOld();
// merge variable types
{
Set<VariableElement> variables = Sets.newHashSet();
variables.addAll(thenVariableTypes.keySet());
variables.addAll(elseVariableTypes.keySet());
for (VariableElement variable : variables) {
List<Type> possibleTypes = Lists.newArrayList();
Type thenType = thenVariableTypes.get(variable);
Type elseType = elseVariableTypes.get(variable);
if (thenType != null && elseType != null) {
possibleTypes.add(thenType);
possibleTypes.add(elseType);
}
if (thenType != null && elseType == null) {
possibleTypes.add(thenType);
possibleTypes.add(variable.getType());
}
if (thenType == null && elseType != null) {
possibleTypes.add(variable.getType());
possibleTypes.add(elseType);
}
// do merge
Type mergedType = types.intersection(possibleTypes);
TypeQuality mergedTypeQuality = Types.getIntersectionQuality(possibleTypes);
setVariableElementType(variable, mergedType, mergedTypeQuality);
}
}
// done
return voidType;
}
@Override
public Type visitInitializer(DartInitializer node) {
DartIdentifier name = node.getName();
if (name != null) {
checkAssignable(typeOf(name), node.getValue());
} else {
typeOf(node.getValue());
}
return voidType;
}
@Override
public Type visitLabel(DartLabel node) {
return typeAsVoid(node);
}
@Override
public Type visitMapLiteral(DartMapLiteral node) {
visit(node.getTypeArguments());
InterfaceType type = node.getType();
// The Map literal has an implicit key type of String, so only one parameter is
// specified <V> where V is the type of the value.
checkAssignable(node, defaultLiteralMapType, type);
// Check the map literal entries against the return type.
{
Type valueType = type.getArguments().get(1);
for (DartMapLiteralEntry literalEntry : node.getEntries()) {
boolean isValueAssignable = checkAssignable(literalEntry, typeOf(literalEntry), valueType);
if (developerModeChecks && !isValueAssignable) {
typeError(literalEntry, ResolverErrorCode.MAP_LITERAL_ELEMENT_TYPE, valueType);
}
}
}
// Check that each key literal is unique.
Set<String> keyValues = Sets.newHashSet();
for (DartMapLiteralEntry literalEntry : node.getEntries()) {
if (literalEntry.getKey() instanceof DartStringLiteral) {
DartStringLiteral keyLiteral = (DartStringLiteral) literalEntry.getKey();
String keyValue = keyLiteral.getValue();
if (keyValues.contains(keyValue)) {
typeError(keyLiteral, TypeErrorCode.MAP_LITERAL_KEY_UNIQUE);
}
keyValues.add(keyValue);
}
}
return type;
}
@Override
public Type visitMapLiteralEntry(DartMapLiteralEntry node) {
nonVoidTypeOf(node.getKey());
return nonVoidTypeOf(node.getValue());
}
@Override
public Type visitMethodDefinition(DartMethodDefinition node) {
MethodElement methodElement = node.getElement();
Modifiers modifiers = methodElement.getModifiers();
DartTypeNode returnTypeNode = node.getFunction().getReturnTypeNode();
if (modifiers.isFactory()
&& ElementKind.of(methodElement).equals(ElementKind.CONSTRUCTOR)) {
analyzeFactory(node.getName(), (ConstructorElement) methodElement);
} else if (modifiers.isSetter()) {
if (returnTypeNode != null && returnTypeNode.getType() != voidType) {
typeError(returnTypeNode, TypeErrorCode.SETTER_RETURN_TYPE, methodElement.getName());
}
if (methodElement.getParameters().size() > 0) {
Element parameterElement = methodElement.getParameters().get(0);
Type setterType = parameterElement.getType();
MethodElement getterElement = Elements.lookupFieldElementGetter(
methodElement.getEnclosingElement(), methodElement.getName());
if (getterElement != null) {
// prepare "getter" type
Type getterType;
// prepare super types between "getter" and "setter" enclosing types
Type getterDeclarationType = getterElement.getEnclosingElement().getType();
List<InterfaceType> superTypes;
if (currentClass != null) {
superTypes = getIntermediateSuperTypes(currentClass, getterDeclarationType);
} else {
superTypes = Lists.newArrayList();
}
// convert "getter" function type to use "setter" type parameters
FunctionType getterFunctionType = (FunctionType) getterElement.getType();
for (InterfaceType superType : superTypes) {
List<Type> superArguments = superType.getArguments();
List<Type> superParameters = superType.getElement().getTypeParameters();
getterFunctionType = (FunctionType) getterFunctionType.subst(superArguments,
superParameters);
}
// get return type
getterType = getterFunctionType.getReturnType();
// compare "getter" and "setter" types
if (!types.isAssignable(setterType, getterType)) {
typeError(parameterElement, TypeErrorCode.SETTER_TYPE_MUST_BE_ASSIGNABLE,
setterType.getElement().getName(), getterType.getElement().getName());
}
// getter and setter should have same "static" flag
if (modifiers.isStatic() != getterElement.getModifiers().isStatic()) {
onError(node.getName(), ResolverErrorCode.FIELD_GETTER_SETTER_SAME_STATIC);
}
}
}
}
// operator == should return "bool"
if (modifiers.isOperator() && methodElement.getName().equals("==")
&& returnTypeNode != null) {
Type returnType = node.getElement().getFunctionType().getReturnType();
if (!Objects.equal(returnType, boolType)) {
typeError(returnTypeNode, TypeErrorCode.OPERATOR_EQUALS_BOOL_RETURN_TYPE);
}
}
// operator "[]=" should return void
if (modifiers.isOperator() && methodElement.getName().equals("[]=")
&& returnTypeNode != null) {
Type returnType = node.getElement().getFunctionType().getReturnType();
if (TypeKind.of(returnType) != TypeKind.VOID) {
typeError(returnTypeNode, TypeErrorCode.OPERATOR_INDEX_ASSIGN_VOID_RETURN_TYPE);
}
}
// visit children
MethodElement prevMethod = currentMethod;
currentMethod = methodElement;
try {
return typeAsVoid(node);
} finally {
currentMethod = prevMethod;
}
}
/**
* @return "super" {@link InterfaceType}s used in declarations from "subType" to "superType",
* first item is given "superType". May be empty, but not <code>null</code>.
*/
private List<InterfaceType> getIntermediateSuperTypes(InterfaceType subType, Type superType) {
LinkedList<InterfaceType> superTypes = Lists.newLinkedList();
InterfaceType t = subType.getElement().getSupertype();
while (t != null) {
superTypes.addFirst(t);
if (Objects.equal(t.getElement().getType(), superType)) {
break;
}
t = t.getElement().getSupertype();
}
return superTypes;
}
private void analyzeFactory(DartExpression name, final ConstructorElement methodElement) {
ASTVisitor<Void> visitor = new ASTVisitor<Void>() {
@Override
public Void visitParameterizedTypeNode(DartParameterizedTypeNode node) {
DartExpression expression = node.getExpression();
Element e = null;
if (expression instanceof DartIdentifier) {
e = ((DartIdentifier) expression).getElement();
} else if (expression instanceof DartPropertyAccess) {
e = ((DartPropertyAccess) expression).getElement();
}
if (!ElementKind.of(e).equals(ElementKind.CLASS)) {
return null;
}
List<DartTypeParameter> parameterNodes = node.getTypeParameters();
assert (parameterNodes.size() == 0);
return null;
}
};
name.accept(visitor);
}
@Override
public Type visitNewExpression(DartNewExpression node) {
ConstructorElement constructorElement = node.getElement();
DartTypeNode typeNode = Types.constructorTypeNode(node);
Type type = null;
// When using a constructor defined in an interface, the bounds can be tighter
// in the default class than defined in the interface.
if (TypeKind.of(typeNode.getType()).equals(TypeKind.INTERFACE)
&& ((InterfaceType)typeNode.getType()).getElement().isInterface()) {
InterfaceType itype = (InterfaceType)typeNode.getType();
ClassElement interfaceElement = itype.getElement();
InterfaceType defaultClassType = interfaceElement.getDefaultClass();
if (defaultClassType != null && defaultClassType.getElement() != null) {
validateBounds(typeNode.getTypeArguments(),
itype.getArguments(),
defaultClassType.getElement().getTypeParameters());
type = itype;
}
}
if (type == null) {
type = validateTypeNode(typeNode);
}
DartNode typeName = typeNode.getIdentifier();
if (constructorElement == null) {
visit(node.getArguments());
} else {
ClassElement cls = (ClassElement) constructorElement.getEnclosingElement();
// Add warning for instantiating abstract class.
if (cls.getModifiers().isAbstract()) {
if (!constructorElement.getModifiers().isFactory()) {
typeError(typeName, TypeErrorCode.INSTANTIATION_OF_ABSTRACT_CLASS, cls.getName());
}
}
// Check type arguments.
FunctionType ftype = (FunctionType) constructorElement.getType();
if (ftype != null && TypeKind.of(type).equals(TypeKind.INTERFACE)) {
InterfaceType ifaceType = (InterfaceType) type;
List<Type> substParams;
if (ifaceType.getElement().isInterface()) {
// The constructor in the interface is resolved to the type parameters declared in
// the interface, but the constructor body has type parameters resolved to the type
// parameters in the default class. This substitution patches up the type variable
// references used in parameters so they match the concrete class.
substParams = ((ClassElement)constructorElement.getEnclosingElement()).getType().getArguments();
} else {
substParams = ifaceType.getElement().getTypeParameters();
}
List<Type> arguments = ifaceType.getArguments();
ftype = (FunctionType) ftype.subst(arguments, substParams);
checkDeprecated(ASTNodes.getConstructorNameNode(node), constructorElement);
checkInvocation(node, node, constructorElement.getName(), ftype);
}
}
type.getClass(); // quick null check
return type;
}
/**
* @param cls the {@link ClassElement} which has unimplemented members.
* @param unimplementedMembers the unimplemented members {@link Element}s.
* @return the {@link StringBuilder} with message about unimplemented members.
*/
private StringBuilder getUnimplementedMembersMessage(ClassElement cls,
List<Element> unimplementedMembers) {
// Prepare groups of unimplemented members for each type.
Multimap<String, String> membersByTypes = ArrayListMultimap.create();
for (Element member : unimplementedMembers) {
ClassElement enclosingElement = (ClassElement) member.getEnclosingElement();
InterfaceType instance = types.asInstanceOf(cls.getType(), enclosingElement);
Type memberType = member.getType().subst(instance.getArguments(),
enclosingElement.getTypeParameters());
if (memberType.getKind().equals(TypeKind.FUNCTION)) {
FunctionType ftype = (FunctionType) memberType;
StringBuilder sb = new StringBuilder();
sb.append(ftype.getReturnType());
sb.append(" ");
sb.append(member.getName());
String string = ftype.toString();
sb.append(string, 0, string.lastIndexOf(" -> "));
membersByTypes.put(enclosingElement.getName(), sb.toString());
} else {
StringBuilder sb = new StringBuilder();
sb.append(memberType);
sb.append(" ");
sb.append(member.getName());
membersByTypes.put(enclosingElement.getName(), sb.toString());
}
}
// Output unimplemented members with grouping by class.
StringBuilder sb = new StringBuilder();
for (String typeName : membersByTypes.keySet()) {
sb.append("\n # From ");
sb.append(typeName);
sb.append(":");
for (String memberString : membersByTypes.get(typeName)) {
sb.append("\n ");
sb.append(memberString);
}
}
return sb;
}
@Override
public Type visitNullLiteral(DartNullLiteral node) {
return nullType;
}
@Override
public Type visitParameter(DartParameter node) {
VariableElement parameter = node.getElement();
FieldElement initializerElement = parameter.getParameterInitializerElement();
if (initializerElement != null) {
checkAssignable(node, parameter.getType(), initializerElement.getType());
}
return checkInitializedDeclaration(node, node.getDefaultExpr());
}
@Override
public Type visitParenthesizedExpression(DartParenthesizedExpression node) {
Type type = nonVoidTypeOf(node.getExpression());
type.getClass(); // quick null check
return type;
}
@Override
public Type visitPropertyAccess(DartPropertyAccess node) {
if (node.getType() != null) {
return node.getType();
}
Element element = node.getElement();
if (element != null) {
return element.getType();
}
if (node.getName().isResolutionAlreadyReportedThatTheMethodCouldNotBeFound()) {
return dynamicType;
}
DartNode qualifier = node.getRealTarget();
Type receiver = nonVoidTypeOf(qualifier);
// convert into InterfaceType
InterfaceType cls = types.getInterfaceType(receiver);
if (cls == null) {
return diagnoseNonInterfaceType(qualifier, receiver);
}
// Do not visit the name, it may not have been resolved.
String name = node.getPropertyName();
InterfaceType.Member member = cls.lookupMember(name);
if (member == null || ASTNodes.inSetterContext(node)) {
InterfaceType.Member member2 = cls.lookupMember("setter " + name);
if (member2 != null && (member == null || member2.getHolder() == member.getHolder() || types.isSubtype(member2.getHolder(), member.getHolder()))) {
member = member2;
}
}
// is "receiver" is inferred, attempt to find member in one of the subtypes
if (member == null) {
if (TypeQuality.of(receiver) == TypeQuality.INFERRED && receiver instanceof InterfaceType) {
member = ((InterfaceType) receiver).lookupSubTypeMember(name);
}
}
// report "not a member"
if (member == null) {
if (reportNoMemberWhenHasInterceptor || !Elements.handlesNoSuchMethod(cls)) {
if (typeChecksForInferredTypes || !TypeQuality.isInferred(receiver)) {
TypeErrorCode errorCode = TypeQuality.isInferred(receiver)
? TypeErrorCode.NOT_A_MEMBER_OF_INFERRED : TypeErrorCode.NOT_A_MEMBER_OF;
typeError(node.getName(), errorCode, name, cls);
}
}
return dynamicType;
}
// set resolved element
element = member.getElement();
node.setElement(element);
Modifiers modifiers = element.getModifiers();
if (modifiers.isStatic()) {
return typeError(node.getName(),
TypeErrorCode.STATIC_MEMBER_ACCESSED_THROUGH_INSTANCE,
name, element.getName());
}
// @deprecated
if (element != null && element.getMetadata().isDeprecated()) {
onError(node.getName(), TypeErrorCode.DEPRECATED_ELEMENT,
Elements.getDeprecatedElementTitle(element));
}
// analyze Element
switch (element.getKind()) {
case DYNAMIC:
return dynamicType;
case CONSTRUCTOR:
return typeError(node.getName(), TypeErrorCode.MEMBER_IS_A_CONSTRUCTOR,
name, element.getName());
case METHOD:
return member.getType();
case FIELD:
FieldElement fieldElement = (FieldElement) element;
Modifiers fieldModifiers = fieldElement.getModifiers();
MethodElement getter = fieldElement.getGetter();
MethodElement setter = fieldElement.getSetter();
boolean inSetterContext = ASTNodes.inSetterContext(node);
boolean inGetterContext = ASTNodes.inGetterContext(node);
ClassElement enclosingClass = null;
if (fieldElement.getEnclosingElement() instanceof ClassElement) {
enclosingClass = (ClassElement) fieldElement.getEnclosingElement();
}
// Implicit field declared as "final".
if (!fieldModifiers.isAbstractField() && fieldModifiers.isFinal() && inSetterContext) {
return typeError(node.getName(), TypeErrorCode.FIELD_IS_FINAL, node.getName());
}
// Check for cases when property has no setter or getter.
if (fieldModifiers.isAbstractField() && enclosingClass != null) {
// Check for using field without setter in some assignment variant.
if (inSetterContext) {
if (setter == null) {
setter = Elements.lookupFieldElementSetter(enclosingClass, name);
if (setter == null) {
setter = Elements.lookupFieldElementSetter(enclosingClass, "setter " + name);
}
if (setter == null) {
return typeError(node.getName(), TypeErrorCode.FIELD_HAS_NO_SETTER, node.getName());
}
}
}
// Check for using field without getter in other operation that assignment.
if (inGetterContext) {
if (getter == null) {
getter = Elements.lookupFieldElementGetter(enclosingClass, name);
if (getter == null) {
return typeError(node.getName(), TypeErrorCode.FIELD_HAS_NO_GETTER, node.getName());
}
}
}
}
Type result = member.getType();
if (fieldModifiers.isAbstractField()) {
if (inSetterContext) {
result = member.getSetterType();
if (result == null) {
return typeError(node.getName(), TypeErrorCode.FIELD_HAS_NO_SETTER, node.getName());
}
}
if (inGetterContext) {
result = member.getGetterType();
if (result == null) {
return typeError(node.getName(), TypeErrorCode.FIELD_HAS_NO_GETTER, node.getName());
}
}
}
node.setType(result);
return result;
default:
throw internalError(node.getName(), "unexpected kind %s", element.getKind());
}
}
@Override
public Type visitReturnStatement(DartReturnStatement node) {
DartExpression value = node.getValue();
if (value == null) {
if (!types.isSubtype(voidType, expected)) {
typeError(node, TypeErrorCode.MISSING_RETURN_VALUE, expected);
}
} else {
checkAssignable(value == null ? node : value, expected, typeOf(value));
}
return voidType;
}
@Override
public Type visitSuperExpression(DartSuperExpression node) {
if (currentClass == null) {
return dynamicType;
}
Type type = currentClass.getElement().getSupertype();
type.getClass(); // quick null check
return type;
}
@Override
public Type visitSwitchStatement(DartSwitchStatement node) {
node.visitChildren(this);
// analyze "expression"
DartExpression expression = node.getExpression();
Type switchType = nonVoidTypeOf(expression);
// check "case" expressions compatibility
Type sameCaseType = null;
for (DartSwitchMember switchMember : node.getMembers()) {
if (switchMember instanceof DartCase) {
DartCase caseMember = (DartCase) switchMember;
DartExpression caseExpr = caseMember.getExpr();
// no expression, parser already reported about this
if (caseExpr == null) {
continue;
}
Type caseType = nonVoidTypeOf(caseExpr);
// all "case expressions" should be same type
if (sameCaseType == null) {
sameCaseType = caseType;
}
if (!Objects.equal(caseType, sameCaseType)) {
onError(caseExpr, TypeErrorCode.CASE_EXPRESSIONS_SHOULD_BE_SAME_TYPE, sameCaseType,
caseType);
}
// compatibility of "switch expression" and "case expression" types
checkAssignable(caseExpr, switchType, caseType);
// should not have "operator =="
if (!Objects.equal(caseType, intType) && !Objects.equal(caseType, doubleType)
&& !Objects.equal(caseType, stringType)) {
Member operator = lookupMember(caseType, methodNameForBinaryOperator(Token.EQ), null);
if (operator != null && !Objects.equal(operator.getHolder(), objectType)) {
onError(caseExpr, TypeErrorCode.CASE_EXPRESSION_TYPE_SHOULD_NOT_HAVE_EQUALS, caseType);
}
}
}
}
return voidType;
}
@Override
public Type visitSyntheticErrorExpression(DartSyntheticErrorExpression node) {
return dynamicType;
}
@Override
public Type visitSyntheticErrorStatement(DartSyntheticErrorStatement node) {
return dynamicType;
}
@Override
public Type visitThisExpression(DartThisExpression node) {
Type type = getCurrentClass();
if (type == null) {
// this was used in a static context, so it should have already generated a fatal error
return voidType;
}
return type;
}
@Override
public Type visitThrowExpression(DartThrowExpression node) {
if (catchDepth == 0 && node.getException() == null) {
context.onError(new DartCompilationError(node,
ResolverErrorCode.RETHROW_NOT_IN_CATCH));
}
return typeAsVoid(node);
}
@Override
public Type visitCatchBlock(DartCatchBlock node) {
++catchDepth;
typeOf(node.getException());
// TODO(karlklose) Check type of stack trace variable.
typeOf(node.getStackTrace());
typeOf(node.getBlock());
--catchDepth;
return voidType;
}
@Override
public Type visitTryStatement(DartTryStatement node) {
return typeAsVoid(node);
}
@Override
public Type visitUnaryExpression(DartUnaryExpression node) {
DartExpression expression = node.getArg();
Type type = nonVoidTypeOf(expression);
Token operator = node.getOperator();
switch (operator) {
case BIT_NOT:
// Bit operations are only supported by integers and
// thus cannot be looked up on num. To ease usage of
// bit operations, we currently allow them to be used
// if the left-hand-side is of type num.
// TODO(karlklose) find a clean solution, i.e., without a special case for num.
if (type.equals(numType)) {
return intType;
} else {
String name = methodNameForUnaryOperator(node, operator);
HasSourceInfo problemTarget = getOperatorHasSourceInfo(node);
Member member = lookupMember(type, name, problemTarget);
if (member != null) {
Element element = member.getElement();
node.setElement(element);
FunctionType methodType = getMethodType(type, member, name, node);
checkDeprecated(problemTarget, element);
return checkInvocation(Collections.<DartExpression>emptyList(), node, name, methodType, null);
} else {
return dynamicType;
}
}
case NOT:
checkAssignable(boolType, expression);
return boolType;
case SUB:
case INC:
case DEC: {
if (type.getElement().isDynamic()) {
return type;
}
InterfaceType itype = types.getInterfaceType(type);
String operatorMethodName = methodNameForUnaryOperator(node, operator);
if (operator == Token.DEC) {
operatorMethodName = "operator -binary";
}
Member member = itype.lookupMember(operatorMethodName);
// TODO(scheglov) remove after library migration from "operator negate()" to "operator -()"
if (member == null && operator == Token.SUB) {
operatorMethodName = "operator negate";
member = itype.lookupMember(operatorMethodName);
}
if (member == null) {
HasSourceInfo errorTarget = getOperatorHasSourceInfo(node);
return typeError(errorTarget, TypeErrorCode.CANNOT_BE_RESOLVED,
operatorMethodName);
}
MethodElement element = ((MethodElement) member.getElement());
node.setElement(element);
Type returnType = ((FunctionType) member.getType()).getReturnType();
if (operator == Token.INC || operator == Token.DEC) {
// For INC and DEC, "operator +" and "operator -" are used to add and subtract one,
// respectively. Check that the resolved operator has a compatible parameter type.
Iterator<VariableElement> it = element.getParameters().iterator();
Type operandType = it.next().getType();
if (!types.isAssignable(numType, operandType)) {
typeError(node, TypeErrorCode.OPERATOR_WRONG_OPERAND_TYPE,
operatorMethodName, numType.toString(), operandType.toString());
}
// Check that the return type of the operator is compatible with the receiver.
checkAssignable(node, type, returnType);
}
return node.isPrefix() ? returnType : type;
}
case CONDITIONAL:
return boolType;
default:
throw internalError(node, "unknown operator %s", operator.toString());
}
}
@Override
public Type visitUnit(DartUnit node) {
blockOldTypes.addFirst(new BlockTypeContext());
return typeAsVoid(node);
}
@Override
public Type visitAssertStatement(DartAssertStatement node) {
DartExpression condition = node.getCondition();
checkAssertCondition(condition);
// infer types, which are valid until the end of the enclosing control block
if (node.getParent() instanceof DartBlock) {
DartBlock restoreBlock = getBlockForAssertTypesInference(node);
VariableElementsRestorer variableRestorer = restoreOnBlockExit.get(restoreBlock);
if (variableRestorer == null) {
variableRestorer = new VariableElementsRestorer();
restoreOnBlockExit.put(restoreBlock, variableRestorer);
}
restoreOnBlockExit.put(restoreBlock, variableRestorer);
inferVariableTypesFromIsConditions(condition, variableRestorer);
}
// done for "assert"
return voidType;
}
@Override
public Type visitUnqualifiedInvocation(DartUnqualifiedInvocation node) {
DartIdentifier target = node.getTarget();
String name = target.getName();
Element element = target.getElement();
node.setElement(element);
Type type;
switch (ElementKind.of(element)) {
case FIELD:
case METHOD:
type = typeAsMemberOf(element, currentClass);
break;
case NONE:
if (!target.isResolutionAlreadyReportedThatTheMethodCouldNotBeFound()) {
onError(target, TypeErrorCode.INTERFACE_HAS_NO_METHOD_NAMED, currentClass, target);
}
return dynamicType;
default:
type = element.getType();
// attempt to resolve to "call()" method invocation
if (type instanceof InterfaceType) {
InterfaceType interfaceType = (InterfaceType) type;
Element callElement = interfaceType.getElement().lookupLocalElement("call");
if (ElementKind.of(callElement) == ElementKind.METHOD) {
node.setElement(callElement);
type = typeAsMemberOf(callElement, interfaceType);
}
}
break;
}
checkDeprecated(target, element);
Type returnType = checkInvocation(node, target, name, type);
returnType = ExternalTypeAnalyzers.resolve(types, node, element, returnType);
return returnType;
}
private static DartBlock getBlockForAssertTypesInference(DartNode node) {
while (node != null) {
if (node instanceof DartBlock) {
DartBlock block = (DartBlock) node;
DartNode p = block.getParent();
if (p instanceof DartIfStatement || p instanceof DartForStatement
|| p instanceof DartForInStatement || p instanceof DartWhileStatement) {
return block;
}
}
node = node.getParent();
}
return null;
}
private static DartBlock getBlockForLoopTypesInference(DartNode node) {
while (node != null) {
if (node instanceof DartBlock) {
DartBlock block = (DartBlock) node;
DartNode p = block.getParent();
if (p instanceof DartForStatement || p instanceof DartForInStatement
|| p instanceof DartWhileStatement) {
return block;
}
}
node = node.getParent();
}
return null;
}
/**
* Report warning if given {@link Element} is deprecated.
*/
private void checkDeprecated(HasSourceInfo nameNode, Element element) {
if (element != null && element.getMetadata().isDeprecated()) {
onError(nameNode, TypeErrorCode.DEPRECATED_ELEMENT,
Elements.getDeprecatedElementTitle(element));
}
}
private Type checkInvocation(DartInvocation node, DartNode diagnosticNode, String name,
Type type) {
List<DartExpression> argumentNodes = node.getArguments();
List<VariableElement> parameters;
if (node.getElement() instanceof MethodElement) {
MethodElement methodElement = (MethodElement) node.getElement();
parameters = methodElement.getParameters();
} else {
parameters = null;
}
return checkInvocation(argumentNodes, diagnosticNode, name, type, parameters);
}
private Type checkInvocation(List<DartExpression> argumentNodes, DartNode diagnosticNode,
String name, Type type, List<VariableElement> parameters) {
// Prepare argument types.
List<Type> argumentTypes = Lists.newArrayListWithCapacity(argumentNodes.size());
for (DartExpression argumentNode : argumentNodes) {
Type argumentType = getInvocationArgumentType(argumentNode);
argumentTypes.add(argumentType);
}
// Check that argument types are compatible with type of invoked object.
try {
switch (TypeKind.of(type)) {
case FUNCTION_ALIAS:
return checkArguments(diagnosticNode, argumentNodes, argumentTypes.iterator(),
Types.asFunctionType((FunctionAliasType) type), parameters);
case FUNCTION:
return checkArguments(diagnosticNode, argumentNodes, argumentTypes.iterator(),
(FunctionType) type, parameters);
case DYNAMIC:
return type;
default:
if (types.isAssignable(functionType, type)) {
// A subtype of interface Function.
return dynamicType;
} else if (name == null || currentClass == null) {
return typeError(diagnosticNode, TypeErrorCode.NOT_A_FUNCTION_TYPE, type);
} else {
return typeError(diagnosticNode, TypeErrorCode.NOT_A_METHOD_IN, name, currentClass);
}
}
} finally {
// In any case visit body of function literals, so use inferred parameter types.
for (DartExpression argument : argumentNodes) {
if (argument instanceof DartNamedExpression) {
argument = ((DartNamedExpression) argument).getExpression();
}
if (argument instanceof DartFunctionExpression) {
argument.accept(this);
}
}
}
}
/**
* Almost same as {@link #nonVoidTypeOf(DartNode)}, but does not visit
* {@link DartFunctionExpression}, because we know its type already and want to infer types of
* arguments, and then propagate them into body.
*/
private Type getInvocationArgumentType(DartExpression argument) {
// We are interesting in the type of expression, without name.
if (argument instanceof DartNamedExpression) {
argument = ((DartNamedExpression) argument).getExpression();
}
// Don't visit function literal, we know its "declared" type.
// But we want to visit it later, to use "inferred" type in body.
if (argument instanceof DartFunctionExpression) {
return argument.getElement().getType();
}
// General case - visit and prepare type.
return nonVoidTypeOf(argument);
}
/**
* Return the type of member as if it was a member of subtype. For example, the type of t in Sub
* should be String, not T:
*
* <pre>
* class Super&lt;T> {
* T t;
* }
* class Sub extends Super&lt;String> {
* }
* </pre>
*/
private Type typeAsMemberOf(Element member, InterfaceType subtype) {
Element holder = member.getEnclosingElement();
if (!ElementKind.of(holder).equals(ElementKind.CLASS)) {
return member.getType();
}
ClassElement superclass = (ClassElement) holder;
InterfaceType supertype = types.asInstanceOf(subtype, superclass);
Type type = member.getType().subst(supertype.getArguments(),
supertype.getElement().getTypeParameters());
type.getClass(); // quick null check
return type;
}
@Override
public Type visitVariable(DartVariable node) {
DartExpression value = node.getValue();
// if type is declared and right side is closure, infer its parameter types
if (value != null) {
Type varType = node.getElement().getType();
if (isExplicitlySpecifiedType(varType)) {
inferFunctionLiteralParametersTypes(value, varType);
}
}
// prepare type of value
Type result = checkInitializedDeclaration(node, value);
// if no type declared for variables, try to use type of value
if (value != null) {
inferVariableDeclarationType(node, value);
}
// done
return result;
}
@Override
public Type visitWhileStatement(DartWhileStatement node) {
DartExpression condition = node.getCondition();
checkCondition(condition);
// visit body
BlockTypeContext blockTypeContxt = new BlockTypeContext();
blockOldTypes.addFirst(blockTypeContxt);
try {
visitConditionalNode(condition, node.getBody());
} finally {
blockOldTypes.removeFirst();
setMergedVariableTypes(blockTypeContxt);
}
// done
return voidType;
}
@Override
public Type visitNamedExpression(DartNamedExpression node) {
// TODO(jgw): Checking of named parameters in progress.
// Intentionally skip the expression's name -- it's stored as an identifier, but doesn't need
// to be resolved or type-checked.
Type type = nonVoidTypeOf(node.getExpression());
type.getClass(); // quick null check
return type;
}
@Override
public Type visitTypeExpression(DartTypeExpression node) {
return typeOf(node.getTypeNode());
}
@Override
public Type visitTypeParameter(DartTypeParameter node) {
if (node.getBound() != null) {
validateTypeNode(node.getBound());
}
return voidType;
}
@Override
public Type visitNativeBlock(DartNativeBlock node) {
return typeAsVoid(node);
}
@Override
public void visit(List<? extends DartNode> nodes) {
if (nodes != null) {
for (DartNode node : nodes) {
node.accept(this);
}
}
}
@Override
public Type visitArrayLiteral(DartArrayLiteral node) {
visit(node.getTypeArguments());
InterfaceType interfaceType = node.getType();
if (interfaceType != null
&& interfaceType.getArguments() != null
&& interfaceType.getArguments().size() > 0) {
Type elementType = interfaceType.getArguments().get(0);
for (DartExpression expression : node.getExpressions()) {
boolean isValueAssignable = checkAssignable(elementType, expression);
if (developerModeChecks && !isValueAssignable) {
typeError(expression, ResolverErrorCode.LIST_LITERAL_ELEMENT_TYPE, elementType);
}
}
}
return interfaceType;
}
@Override
public Type visitBooleanLiteral(DartBooleanLiteral node) {
return typeOfLiteral(node);
}
@Override
public Type visitDoubleLiteral(DartDoubleLiteral node) {
return typeOfLiteral(node);
}
@Override
public Type visitField(DartField node) {
DartMethodDefinition accessor = node.getAccessor();
if (accessor != null) {
return typeOf(accessor);
} else {
DartExpression value = node.getValue();
// if type is declared and right side is closure, infer its parameter types
if (value != null) {
Type fieldType = node.getElement().getType();
if (isExplicitlySpecifiedType(fieldType)) {
inferFunctionLiteralParametersTypes(value, fieldType);
}
}
// prepare type of value
Type result = checkInitializedDeclaration(node, value);
// if no type declared for field, try to use type of value
// only final fields, because only in this case we can be sure that field is not assigned
// somewhere, may be even not in this unit
if (node.getModifiers().isFinal() && value != null) {
inferVariableDeclarationType(node, value);
}
// done
return result;
}
}
private Type checkInitializedDeclaration(DartDeclaration<?> node, DartExpression value) {
if (value != null && node.getElement() != null) {
checkAssignable(node.getElement().getType(), value);
}
return voidType;
}
/**
* @return <code>true</code> if given {@link FunctionAliasElement} has direct or indirect
* reference to itself using other {@link FunctionAliasElement}s.
*/
private boolean hasFunctionTypeAliasSelfReference(FunctionAliasElement target) {
Set<FunctionAliasElement> visited = Sets.newHashSet();
return hasFunctionTypeAliasReference(visited, target, target);
}
/**
* Checks if "target" is referenced by "current".
*/
private boolean hasFunctionTypeAliasReference(Set<FunctionAliasElement> visited,
FunctionAliasElement target, FunctionAliasElement current) {
FunctionType type = current.getFunctionType();
// prepare Types directly referenced by "current"
Set<Type> referencedTypes = Sets.newHashSet();
if (type != null) {
// type parameters
for (Type typeParameter : current.getTypeParameters()) {
if (typeParameter instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) typeParameter;
Type bound = typeVariable.getTypeVariableElement().getBound();
referencedTypes.add(bound);
}
}
// return type
referencedTypes.add(type.getReturnType());
// parameters
referencedTypes.addAll(type.getParameterTypes());
referencedTypes.addAll(type.getOptionalParameterTypes().values());
referencedTypes.addAll(type.getNamedParameterTypes().values());
}
// check that referenced types do not have references on "target"
for (Type referencedType : referencedTypes) {
if (referencedType != null
&& hasFunctionTypeAliasReference(visited, target, referencedType.getElement())) {
return true;
}
}
// no
return false;
}
/**
* Checks if "target" is referenced by "current".
*/
private boolean hasFunctionTypeAliasReference(Set<FunctionAliasElement> visited,
FunctionAliasElement target, Element currentElement) {
// only if "current" in function type alias
if (!(currentElement instanceof FunctionAliasElement)) {
return false;
}
FunctionAliasElement current = (FunctionAliasElement) currentElement;
// found "target"
if (Objects.equal(target, current)) {
return true;
}
// prevent recursion
if (visited.contains(current)) {
return false;
}
visited.add(current);
// check type of "current" function type alias
return hasFunctionTypeAliasReference(visited, target, current);
}
@Override
public Type visitIntegerLiteral(DartIntegerLiteral node) {
return typeOfLiteral(node);
}
@Override
public Type visitStringLiteral(DartStringLiteral node) {
return typeOfLiteral(node);
}
@Override
public Type visitStringInterpolation(DartStringInterpolation node) {
visit(node.getExpressions());
return typeOfLiteral(node);
}
@Override
public Type visitParameterizedTypeNode(DartParameterizedTypeNode node) {
visit(node.getTypeParameters());
Type type = node.getType();
type.getClass(); // quick null check
return type;
}
@Override
public Type visitImportDirective(DartImportDirective node) {
return voidType;
}
@Override
public Type visitExportDirective(DartExportDirective node) {
return voidType;
}
@Override
public Type visitLibraryDirective(DartLibraryDirective node) {
//return typeAsVoid(node);
return voidType;
}
@Override
public Type visitNativeDirective(DartNativeDirective node) {
return typeAsVoid(node);
}
@Override
public Type visitSourceDirective(DartSourceDirective node) {
return typeAsVoid(node);
}
private class AbstractMethodFinder extends ASTVisitor<Void> {
private final InterfaceType currentClass;
private final Multimap<String, Element> superMembers = LinkedListMultimap.create();
private final List<Element> unimplementedElements = Lists.newArrayList();
private AbstractMethodFinder(InterfaceType currentClass) {
this.currentClass = currentClass;
}
@Override
public Void visitNode(DartNode node) {
throw new AssertionError();
}
@Override
public Void visitClass(DartClass node) {
assert node.getElement().getType() == currentClass;
// Prepare supertypes - all superclasses and interfaces.
List<InterfaceType> supertypes = Collections.emptyList();
boolean hasCyclicDeclaration = false;
try {
supertypes = currentClass.getElement().getAllSupertypes();
} catch (CyclicDeclarationException e) {
// Already reported by resolver.
hasCyclicDeclaration = true;
}
// Add all super members to resolve.
Element currentLibrary = currentClass.getElement().getEnclosingElement();
// cull out duplicate elements in the supertype list - inheriting more than one interface
// of the same type is valid.
Set<ClassElement> typesForAbstractMembers = Sets.newHashSet();
typesForAbstractMembers.add(currentClass.getElement());
for (InterfaceType supertype : supertypes) {
typesForAbstractMembers.add(supertype.getElement());
}
Set<String> artificialNames = Sets.newHashSet();
for (ClassElement interfaceElement : typesForAbstractMembers) {
for (Element member : interfaceElement.getMembers()) {
if (interfaceElement == currentClass.getElement() && !member.getModifiers().isAbstract()) {
continue;
}
String name = member.getName();
if (DartIdentifier.isPrivateName(name)) {
if (currentLibrary != member.getEnclosingElement().getEnclosingElement()) {
continue;
}
}
superMembers.put(name, member);
if (member instanceof FieldElement) {
FieldElement field = (FieldElement) member;
if (!field.getModifiers().isAbstractField()) {
artificialNames.add("setter " + name);
superMembers.put("setter " + name, member);
} else if (field.getSetter() != null && !name.startsWith("setter ")) {
superMembers.put("setter " + name, member);
}
}
}
}
// Visit members, so resolve methods declared in this class.
this.visit(node.getMembers());
// If interface, we don't care about unimplemented methods.
if (currentClass.getElement().isInterface()) {
return null;
}
// If we have cyclic declaration, hierarchy is broken, no reason to report unimplemented.
if (hasCyclicDeclaration) {
return null;
}
// Visit superclasses (without interfaces) and mark methods as implemented.
InterfaceType supertype = currentClass.getElement().getSupertype();
while (supertype != null) {
ClassElement superclass = supertype.getElement();
for (Element member : superclass.getMembers()) {
String name = member.getName();
if (!member.getModifiers().isAbstract()) {
superMembers.removeAll(name);
}
if (member instanceof FieldElement) {
FieldElement field = (FieldElement) member;
if (field.getSetter() != null) {
superMembers.removeAll("setter " + name);
}
}
}
supertype = supertype.getElement().getSupertype();
}
// Remove artificial "setter " members.
for (String name : artificialNames) {
superMembers.removeAll(name);
}
// All remaining methods are unimplemented.
for (String name : superMembers.keys()) {
Collection<Element> elements = superMembers.removeAll(name);
for (Element element : elements) {
if (!element.getModifiers().isStatic()) {
unimplementedElements.add(element);
break; // Only report the first unimplemented element with this name.
}
}
}
// add abstract members of current class
for (Element member : currentClass.getElement().getMembers()) {
if (member.getModifiers().isAbstract()) {
unimplementedElements.add(member);
}
}
return null;
}
@Override
public Void visitFieldDefinition(DartFieldDefinition node) {
this.visit(node.getFields());
return null;
}
@Override
public Void visitField(DartField node) {
if (superMembers == null) {
return null;
}
FieldElement field = node.getElement();
// prepare overridden elements
String name = field.getName();
Set<Element> overridden = Sets.newHashSet();
if (node.getAccessor() != null) {
if (node.getAccessor().getModifiers().isSetter()) {
if (!name.startsWith("setter ")) {
name = "setter " + name;
}
}
overridden.addAll(superMembers.removeAll(name));
} else {
overridden.addAll(superMembers.removeAll(name));
overridden.addAll(superMembers.removeAll("setter " + name));
}
// check override
for (Element superElement : overridden) {
if (!(field.isStatic() && superElement.getModifiers().isStatic())) {
if (canOverride(node.getName(), field.getModifiers(), superElement)
&& !superElement.getModifiers().isStatic()) {
switch (superElement.getKind()) {
case FIELD:
checkOverride(node.getName(), field, superElement);
break;
case METHOD:
typeError(node.getName(), TypeErrorCode.SUPERTYPE_HAS_METHOD, name,
superElement.getEnclosingElement().getName());
break;
default:
typeError(node, TypeErrorCode.INTERNAL_ERROR, superElement);
break;
}
}
}
}
// set super-elements for FieldElement
Elements.setOverridden(field, ImmutableSet.copyOf(overridden));
// set super-elements for getter/setter
if (node.getAccessor() != null) {
Set<Element> superGetters = Sets.newHashSet();
Set<Element> superSetters = Sets.newHashSet();
for (Element superElement : overridden) {
if (superElement instanceof FieldElement) {
FieldElement superField = (FieldElement) superElement;
if (superField.getGetter() != null) {
superGetters.add(superField.getGetter());
} else if (superField.getSetter() != null) {
superSetters.add(superField.getSetter());
} else {
superGetters.add(superField);
superSetters.add(superField);
}
}
}
if (node.getAccessor().getModifiers().isGetter()) {
Elements.setOverridden(node.getAccessor().getElement(), superGetters);
}
if (node.getAccessor().getModifiers().isSetter()) {
Elements.setOverridden(node.getAccessor().getElement(), superSetters);
}
}
return null;
}
@Override
public Void visitMethodDefinition(DartMethodDefinition node) {
MethodElement method = node.getElement();
String name = method.getName();
if (superMembers != null && !method.isConstructor()) {
Collection<Element> overridden = superMembers.removeAll(name);
Elements.setOverridden(method, ImmutableSet.copyOf(overridden));
// Check for invalid @override metadata.
if (overridden.isEmpty() && node.getObsoleteMetadata().isOverride()) {
typeError(node.getName(), ResolverErrorCode.INVALID_OVERRIDE_METADATA);
}
// Check that override is valid.
for (Element superElement : overridden) {
if (!(method.isStatic() && superElement.getModifiers().isStatic())) {
if (canOverride(node.getName(), method.getModifiers(), superElement)
&& !superElement.getModifiers().isStatic()) {
switch (superElement.getKind()) {
case METHOD:
checkOverride(node.getName(), method, superElement);
break;
case FIELD:
typeError(node.getName(), TypeErrorCode.SUPERTYPE_HAS_FIELD, superElement.getName(),
superElement.getEnclosingElement().getName());
break;
default:
typeError(node, TypeErrorCode.INTERNAL_ERROR, superElement);
break;
}
}
}
}
}
return null;
}
/**
* Report a compile-time error if a static member tries to override an instance member
* @returns true if no compile-time error was reported
*/
private boolean canOverride(HasSourceInfo errorTarget, Modifiers modifiers,
Element superElement) {
if (!superElement.getModifiers().isStatic() && modifiers.isStatic()) {
onError(errorTarget, ResolverErrorCode.CANNOT_OVERRIDE_INSTANCE_MEMBER,
superElement.getName(), superElement.getEnclosingElement().getName());
return false;
} else if (superElement.getModifiers().isStatic() && !modifiers.isStatic()) {
onError(errorTarget, TypeErrorCode.OVERRIDING_STATIC_MEMBER,
superElement.getName(), superElement.getEnclosingElement().getName());
// Although a warning, override is allowed anyway
return true;
}
return true;
}
/**
* Report a static type error if member cannot override superElement, that
* is, they are not assignable.
*/
private void checkOverride(HasSourceInfo errorTarget, Element member, Element superElement) {
String name = member.getName();
Type superMember = typeAsMemberOf(superElement, currentClass);
if (member.getKind() == ElementKind.METHOD && superElement.getKind() == ElementKind.METHOD) {
MethodElement method = (MethodElement) member;
MethodElement superMethod = (MethodElement) superElement;
if (hasLegalMethodOverrideSignature(errorTarget, method, superMethod)) {
if (!types.isSubtype(member.getType(), superMember)) {
typeError(errorTarget,
TypeErrorCode.CANNOT_OVERRIDE_METHOD_NOT_SUBTYPE,
name,
superElement.getEnclosingElement().getName(),
member.getType(),
superMember);
}
}
} else if (!types.isAssignable(superMember, member.getType())) {
typeError(errorTarget,
TypeErrorCode.CANNOT_OVERRIDE_TYPED_MEMBER,
name,
superElement.getEnclosingElement().getName(),
member.getType(),
superMember);
}
}
/**
* @return <code>true</code> if given "method" has signature compatible with "superMethod".
*/
private boolean hasLegalMethodOverrideSignature(HasSourceInfo errorTarget,
MethodElement method,
MethodElement superMethod) {
// Prepare parameters.
List<VariableElement> parameters = method.getParameters();
List<VariableElement> superParameters = superMethod.getParameters();
// Number of required parameters should be same.
{
int numRequired = Elements.getNumberOfRequiredParameters(method);
int superNumRequired = Elements.getNumberOfRequiredParameters(superMethod);
if (numRequired != superNumRequired) {
onError(errorTarget,
ResolverErrorCode.CANNOT_OVERRIDE_METHOD_NUM_REQUIRED_PARAMS,
getMethodSignature(superMethod),
superMethod.getEnclosingElement().getName());
return false;
}
}
// TODO(scheglov) remove after http://code.google.com/p/dart/issues/detail?id=6306
if (Elements.isLibrarySource(errorTarget.getSourceInfo().getSource(), "/io/io_runtime.dart")) {
return true;
}
// "method" should have at least same number of optional positional parameters as "superMethod"
{
LinkedHashMap<VariableElement, DartExpression> defMethod = getParametersDefaultsOptional(parameters);
LinkedHashMap<VariableElement, DartExpression> defSuper = getParametersDefaultsOptional(superParameters);
if (defMethod.size() < defSuper.size()) {
onError(errorTarget,
ResolverErrorCode.CANNOT_OVERRIDE_METHOD_OPTIONAL_PARAMS,
getMethodSignature(superMethod),
superMethod.getEnclosingElement().getName());
return false;
}
Iterator<Entry<VariableElement, DartExpression>> defMethodIter = defMethod.entrySet().iterator();
Iterator<Entry<VariableElement, DartExpression>> defSuperIter = defSuper.entrySet().iterator();
while (defSuperIter.hasNext()) {
Entry<VariableElement, DartExpression> methodEntry = defMethodIter.next();
Entry<VariableElement, DartExpression> superEntry = defSuperIter.next();
DartExpression methodValue = methodEntry.getValue();
DartExpression superValue = superEntry.getValue();
if (superValue != null
&& !Objects.equal(ObjectUtils.toString(methodValue),
ObjectUtils.toString(superValue))) {
onError(methodEntry.getKey().getSourceInfo(),
TypeErrorCode.CANNOT_OVERRIDE_METHOD_DEFAULT_VALUE, method.getName(),
superValue);
}
}
}
// "method" should have at least all named parameters of "superMethod".
{
Map<String, VariableElement> methodNames = getParametersNamed(parameters);
Map<String, VariableElement> superNames = getParametersNamed(superParameters);
if (!methodNames.keySet().containsAll(superNames.keySet())) {
onError(errorTarget, ResolverErrorCode.CANNOT_OVERRIDE_METHOD_NAMED_PARAMS, method.getName());
return false;
}
Map<String, DartExpression> methodDefs = getParametersDefaultsNamed(parameters);
Map<String, DartExpression> superDefs = getParametersDefaultsNamed(superParameters);
for (Entry<String, DartExpression> entry : superDefs.entrySet()) {
String name = entry.getKey();
DartExpression defValue = methodDefs.get(name);
DartExpression superDefValue = superDefs.get(name);
if (superDefValue != null
&& !Objects.equal(ObjectUtils.toString(defValue),
ObjectUtils.toString(superDefValue))) {
onError(methodNames.get(name).getSourceInfo(),
TypeErrorCode.CANNOT_OVERRIDE_METHOD_DEFAULT_VALUE, method.getName(),
superDefValue);
}
}
}
// List<VariableElement> named = getNamedParameters(parameters);
// List<VariableElement> superNamed = getNamedParameters(superParameters);
// Iterator<VariableElement> namedIterator = named.iterator();
// Iterator<VariableElement> superNamedIterator = superNamed.iterator();
// while (superNamedIterator.hasNext()) {
// VariableElement superParameter = superNamedIterator.next();
// if (namedIterator.hasNext()) {
// VariableElement parameter = namedIterator.next();
// if (Objects.equal(parameter.getName(), superParameter.getName())) {
// DartExpression superDefValue = superParameter.getDefaultValue();
// DartExpression defValue = parameter.getDefaultValue();
// if (superDefValue != null
// && !Objects.equal(ObjectUtils.toString(defValue),
// ObjectUtils.toString(superDefValue))) {
// onError(parameter.getSourceInfo(),
// TypeErrorCode.CANNOT_OVERRIDE_METHOD_DEFAULT_VALUE, method.getName(),
// superDefValue);
// }
// continue;
// }
// }
// onError(errorTarget, ResolverErrorCode.CANNOT_OVERRIDE_METHOD_NAMED_PARAMS, method.getName());
// return false;
// }
return true;
}
private String getMethodSignature(MethodElement method) {
StringBuilder builder = new StringBuilder();
builder.append(method.getName());
builder.append("(");
boolean inOptional = false;
boolean inNamed = false;
List<VariableElement> superParameters = method.getParameters();
int parameterCount = superParameters.size();
for (int i = 0; i < parameterCount; i++) {
if (i > 0) {
builder.append(", ");
}
VariableElement parameter = superParameters.get(i);
if (!inOptional && parameter.isOptional()) {
builder.append("[");
inOptional = true;
}
if (!inNamed && parameter.isNamed()) {
builder.append("{");
inNamed = true;
}
builder.append(parameter.getType().toString());
}
if (inOptional) {
builder.append("]");
}
if (inNamed) {
builder.append("}");
}
builder.append(")");
String methodSignature = builder.toString();
return methodSignature;
}
private LinkedHashMap<VariableElement, DartExpression> getParametersDefaultsOptional(List<VariableElement> parameters) {
LinkedHashMap<VariableElement, DartExpression> defaults = Maps.newLinkedHashMap();
for (VariableElement parameter : parameters) {
if (parameter.isOptional()) {
defaults.put(parameter, parameter.getDefaultValue());
}
}
return defaults;
}
private Map<String, VariableElement> getParametersNamed(List<VariableElement> parameters) {
Map<String, VariableElement> namedParameters = Maps.newHashMap();
for (VariableElement parameter : parameters) {
if (parameter.isNamed()) {
namedParameters.put(parameter.getName(), parameter);
}
}
return namedParameters;
}
private Map<String, DartExpression> getParametersDefaultsNamed(List<VariableElement> parameters) {
Map<String, DartExpression> defaults = Maps.newHashMap();
for (VariableElement parameter : parameters) {
if (parameter.isNamed()) {
defaults.put(parameter.getName(), parameter.getDefaultValue());
}
}
return defaults;
}
}
}
private static boolean isExplicitlySpecifiedType(Type type) {
return type != null && TypeKind.of(type) != TypeKind.DYNAMIC
&& !TypeQuality.isInferred(type);
}
/**
* @return the {@link TypeQuality} of given {@link DartExpression}.
*/
public static TypeQuality getTypeQuality(DartExpression expr) {
if (expr != null) {
if (expr instanceof DartIdentifier) {
Type varType = expr.getType();
if (varType != null) {
TypeQuality varTypeQuality = varType.getQuality();
if (varTypeQuality == TypeQuality.EXACT) {
varTypeQuality = TypeQuality.INFERRED_EXACT;
}
return varTypeQuality;
}
}
if (expr instanceof DartLiteral) {
return TypeQuality.INFERRED_EXACT;
}
if (expr instanceof DartUnaryExpression) {
DartUnaryExpression unary = (DartUnaryExpression) expr;
if (hasTypeBoolIntDouble(unary.getArg())) {
return TypeQuality.INFERRED_EXACT;
}
return TypeQuality.INFERRED;
}
if (expr instanceof DartBinaryExpression) {
DartBinaryExpression binary = (DartBinaryExpression) expr;
if (hasTypeBoolIntDouble(binary.getArg1()) && hasTypeBoolIntDouble(binary.getArg2())) {
return TypeQuality.INFERRED_EXACT;
}
return TypeQuality.INFERRED;
}
if (expr instanceof DartNewExpression) {
DartNewExpression newExpression = (DartNewExpression) expr;
ConstructorElement constructorElement = newExpression.getElement();
if (constructorElement != null && !constructorElement.getModifiers().isFactory()) {
return TypeQuality.INFERRED_EXACT;
}
return TypeQuality.INFERRED;
}
}
return TypeQuality.INFERRED;
}
private static boolean hasTypeBoolIntDouble(DartExpression expr) {
Type type = expr.getType();
return isCoreType(type, "bool") || isCoreType(type, "int") || isCoreType(type, "double");
}
private static boolean isCoreType(Type type, String name) {
return type != null
&& Elements.isCoreLibrarySource(type.getElement().getSourceInfo().getSource())
&& type.getElement().getName().equals(name);
}
}