/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.types.expressions;
import com.google.common.collect.Sets;
import com.intellij.psi.tree.IElementType;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingContextUtils;
import org.jetbrains.kotlin.resolve.TemporaryBindingTrace;
import org.jetbrains.kotlin.resolve.calls.context.CallPosition;
import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults;
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsImpl;
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory;
import org.jetbrains.kotlin.resolve.lazy.ForceResolveUtil;
import org.jetbrains.kotlin.resolve.scopes.LexicalWritableScope;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.KotlinTypeKt;
import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt;
import org.jetbrains.kotlin.util.OperatorNameConventions;
import java.util.Collection;
import static org.jetbrains.kotlin.diagnostics.Errors.*;
import static org.jetbrains.kotlin.psi.KtPsiUtil.deparenthesize;
import static org.jetbrains.kotlin.resolve.BindingContext.AMBIGUOUS_REFERENCE_TARGET;
import static org.jetbrains.kotlin.resolve.BindingContext.VARIABLE_REASSIGNMENT;
import static org.jetbrains.kotlin.resolve.calls.context.ContextDependency.INDEPENDENT;
import static org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE;
import static org.jetbrains.kotlin.types.TypeUtils.noExpectedType;
@SuppressWarnings("SuspiciousMethodCalls")
public class ExpressionTypingVisitorForStatements extends ExpressionTypingVisitor {
private final LexicalWritableScope scope;
private final BasicExpressionTypingVisitor basic;
private final ControlStructureTypingVisitor controlStructures;
private final PatternMatchingTypingVisitor patterns;
private final FunctionsTypingVisitor functions;
public ExpressionTypingVisitorForStatements(
@NotNull ExpressionTypingInternals facade,
@NotNull LexicalWritableScope scope,
@NotNull BasicExpressionTypingVisitor basic,
@NotNull ControlStructureTypingVisitor controlStructures,
@NotNull PatternMatchingTypingVisitor patterns,
@NotNull FunctionsTypingVisitor functions
) {
super(facade);
this.scope = scope;
this.basic = basic;
this.controlStructures = controlStructures;
this.patterns = patterns;
this.functions = functions;
}
@Nullable
private KotlinType checkAssignmentType(
@Nullable KotlinType assignmentType,
@NotNull KtBinaryExpression expression,
@NotNull ExpressionTypingContext context
) {
if (assignmentType != null && !KotlinBuiltIns.isUnit(assignmentType) && !noExpectedType(context.expectedType) &&
!KotlinTypeKt.isError(context.expectedType) && TypeUtils.equalTypes(context.expectedType, assignmentType)) {
context.trace.report(Errors.ASSIGNMENT_TYPE_MISMATCH.on(expression, context.expectedType));
return null;
}
return components.dataFlowAnalyzer.checkStatementType(expression, context);
}
@Override
public KotlinTypeInfo visitObjectDeclaration(@NotNull KtObjectDeclaration declaration, ExpressionTypingContext context) {
components.localClassifierAnalyzer.processClassOrObject(
scope, context.replaceScope(scope).replaceContextDependency(INDEPENDENT),
scope.getOwnerDescriptor(),
declaration);
return TypeInfoFactoryKt.createTypeInfo(components.dataFlowAnalyzer.checkStatementType(declaration, context), context);
}
@Override
public KotlinTypeInfo visitProperty(@NotNull KtProperty property, ExpressionTypingContext typingContext) {
Pair<KotlinTypeInfo, VariableDescriptor> typeInfoAndVariableDescriptor = components.localVariableResolver.process(property, typingContext, scope, facade);
scope.addVariableDescriptor(typeInfoAndVariableDescriptor.getSecond());
return typeInfoAndVariableDescriptor.getFirst();
}
@Override
public KotlinTypeInfo visitTypeAlias(@NotNull KtTypeAlias typeAlias, ExpressionTypingContext context) {
TypeAliasDescriptor typeAliasDescriptor = components.descriptorResolver.resolveTypeAliasDescriptor(
context.scope.getOwnerDescriptor(), context.scope, typeAlias, context.trace);
scope.addClassifierDescriptor(typeAliasDescriptor);
ForceResolveUtil.forceResolveAllContents(typeAliasDescriptor);
facade.getComponents().declarationsCheckerBuilder.withTrace(context.trace).checkLocalTypeAliasDeclaration(typeAlias, typeAliasDescriptor);
return TypeInfoFactoryKt.createTypeInfo(components.dataFlowAnalyzer.checkStatementType(typeAlias, context), context);
}
@Override
public KotlinTypeInfo visitDestructuringDeclaration(@NotNull KtDestructuringDeclaration multiDeclaration, ExpressionTypingContext context) {
components.annotationResolver.resolveAnnotationsWithArguments(scope, multiDeclaration.getModifierList(), context.trace);
KtExpression initializer = multiDeclaration.getInitializer();
if (initializer == null) {
context.trace.report(INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION.on(multiDeclaration));
}
ExpressionReceiver expressionReceiver = initializer != null ? ExpressionTypingUtils.getExpressionReceiver(
facade, initializer, context.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT)) : null;
components.destructuringDeclarationResolver
.defineLocalVariablesFromDestructuringDeclaration(scope, multiDeclaration, expressionReceiver, initializer, context);
components.modifiersChecker.withTrace(context.trace).checkModifiersForDestructuringDeclaration(multiDeclaration);
components.identifierChecker.checkDeclaration(multiDeclaration, context.trace);
if (expressionReceiver == null) {
return TypeInfoFactoryKt.noTypeInfo(context);
}
else {
return facade.getTypeInfo(initializer, context)
.replaceType(components.dataFlowAnalyzer.checkStatementType(multiDeclaration, context));
}
}
@Override
public KotlinTypeInfo visitNamedFunction(@NotNull KtNamedFunction function, ExpressionTypingContext context) {
return functions.visitNamedFunction(function, context, /* isDeclaration = */ function.getName() != null, scope);
}
@Override
public KotlinTypeInfo visitClass(@NotNull KtClass klass, ExpressionTypingContext context) {
components.localClassifierAnalyzer.processClassOrObject(
scope, context.replaceScope(scope).replaceContextDependency(INDEPENDENT),
scope.getOwnerDescriptor(),
klass);
return TypeInfoFactoryKt.createTypeInfo(components.dataFlowAnalyzer.checkStatementType(klass, context), context);
}
@Override
public KotlinTypeInfo visitDeclaration(@NotNull KtDeclaration dcl, ExpressionTypingContext context) {
return TypeInfoFactoryKt.createTypeInfo(components.dataFlowAnalyzer.checkStatementType(dcl, context), context);
}
@Override
public KotlinTypeInfo visitBinaryExpression(@NotNull KtBinaryExpression expression, ExpressionTypingContext context) {
KtSimpleNameExpression operationSign = expression.getOperationReference();
IElementType operationType = operationSign.getReferencedNameElementType();
KotlinTypeInfo result;
if (operationType == KtTokens.EQ) {
result = visitAssignment(expression, context);
}
else if (OperatorConventions.ASSIGNMENT_OPERATIONS.containsKey(operationType)) {
result = visitAssignmentOperation(expression, context);
}
else {
return facade.getTypeInfo(expression, context);
}
return components.dataFlowAnalyzer.checkType(result, expression, context);
}
@NotNull
protected KotlinTypeInfo visitAssignmentOperation(KtBinaryExpression expression, ExpressionTypingContext contextWithExpectedType) {
//There is a temporary binding trace for an opportunity to resolve set method for array if needed (the initial trace should be used there)
TemporaryTraceAndCache temporary = TemporaryTraceAndCache.create(
contextWithExpectedType, "trace to resolve array set method for binary expression", expression);
ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE)
.replaceTraceAndCache(temporary).replaceContextDependency(INDEPENDENT);
KtSimpleNameExpression operationSign = expression.getOperationReference();
IElementType operationType = operationSign.getReferencedNameElementType();
KtExpression leftOperand = expression.getLeft();
KotlinTypeInfo leftInfo = ExpressionTypingUtils.getTypeInfoOrNullType(leftOperand, context, facade);
KotlinType leftType = leftInfo.getType();
KtExpression right = expression.getRight();
KtExpression left = leftOperand == null ? null : deparenthesize(leftOperand);
if (right == null || left == null) {
temporary.commit();
return leftInfo.clearType();
}
if (leftType == null) {
KotlinTypeInfo rightInfo = facade.getTypeInfo(right, context.replaceDataFlowInfo(leftInfo.getDataFlowInfo()));
context.trace.report(UNRESOLVED_REFERENCE.on(operationSign, operationSign));
temporary.commit();
return rightInfo.clearType();
}
ExpressionReceiver receiver = ExpressionReceiver.Companion.create(left, leftType, context.trace.getBindingContext());
// We check that defined only one of '+=' and '+' operations, and call it (in the case '+' we then also assign)
// Check for '+='
Name name = OperatorConventions.ASSIGNMENT_OPERATIONS.get(operationType);
TemporaryTraceAndCache temporaryForAssignmentOperation = TemporaryTraceAndCache.create(
context, "trace to check assignment operation like '+=' for", expression);
OverloadResolutionResults<FunctionDescriptor> assignmentOperationDescriptors =
components.callResolver.resolveBinaryCall(
context.replaceTraceAndCache(temporaryForAssignmentOperation).replaceScope(scope),
receiver, expression, name
);
KotlinType assignmentOperationType = OverloadResolutionResultsUtil.getResultingType(assignmentOperationDescriptors,
context.contextDependency);
OverloadResolutionResults<FunctionDescriptor> binaryOperationDescriptors;
KotlinType binaryOperationType;
TemporaryTraceAndCache temporaryForBinaryOperation = TemporaryTraceAndCache.create(
context, "trace to check binary operation like '+' for", expression);
TemporaryBindingTrace ignoreReportsTrace = TemporaryBindingTrace.create(context.trace, "Trace for checking assignability");
boolean lhsAssignable = basic.checkLValue(ignoreReportsTrace, context, left, right, expression);
if (assignmentOperationType == null || lhsAssignable) {
// Check for '+'
Name counterpartName = OperatorConventions.BINARY_OPERATION_NAMES.get(OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS.get(operationType));
binaryOperationDescriptors = components.callResolver.resolveBinaryCall(
context.replaceTraceAndCache(temporaryForBinaryOperation).replaceScope(scope),
receiver, expression, counterpartName
);
binaryOperationType = OverloadResolutionResultsUtil.getResultingType(binaryOperationDescriptors, context.contextDependency);
}
else {
binaryOperationDescriptors = OverloadResolutionResultsImpl.nameNotFound();
binaryOperationType = null;
}
KotlinType type = assignmentOperationType != null ? assignmentOperationType : binaryOperationType;
KotlinTypeInfo rightInfo = leftInfo;
boolean hasRemAssignOperation = atLeastOneOperation(assignmentOperationDescriptors.getResultingCalls(), OperatorNameConventions.REM_ASSIGN);
boolean hasRemBinaryOperation = atLeastOneOperation(binaryOperationDescriptors.getResultingCalls(), OperatorNameConventions.REM);
boolean oneTypeOfModRemOperations = hasRemAssignOperation == hasRemBinaryOperation;
if (assignmentOperationDescriptors.isSuccess() && binaryOperationDescriptors.isSuccess() && oneTypeOfModRemOperations) {
// Both 'plus()' and 'plusAssign()' available => ambiguity
OverloadResolutionResults<FunctionDescriptor> ambiguityResolutionResults = OverloadResolutionResultsUtil.ambiguity(assignmentOperationDescriptors, binaryOperationDescriptors);
context.trace.report(ASSIGN_OPERATOR_AMBIGUITY.on(operationSign, ambiguityResolutionResults.getResultingCalls()));
Collection<DeclarationDescriptor> descriptors = Sets.newHashSet();
for (ResolvedCall<?> resolvedCall : ambiguityResolutionResults.getResultingCalls()) {
descriptors.add(resolvedCall.getResultingDescriptor());
}
rightInfo = facade.getTypeInfo(right, context.replaceDataFlowInfo(leftInfo.getDataFlowInfo()));
context.trace.record(AMBIGUOUS_REFERENCE_TARGET, operationSign, descriptors);
}
else if (assignmentOperationType != null &&
(assignmentOperationDescriptors.isSuccess() || !binaryOperationDescriptors.isSuccess()) &&
(!hasRemBinaryOperation || !binaryOperationDescriptors.isSuccess())) {
// There's 'plusAssign()', so we do a.plusAssign(b)
temporaryForAssignmentOperation.commit();
if (!KotlinTypeChecker.DEFAULT.equalTypes(components.builtIns.getUnitType(), assignmentOperationType)) {
context.trace.report(ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT.on(operationSign, assignmentOperationDescriptors.getResultingDescriptor(), operationSign));
}
}
else {
// There's only 'plus()', so we try 'a = a + b'
temporaryForBinaryOperation.commit();
context.trace.record(VARIABLE_REASSIGNMENT, expression);
if (left instanceof KtArrayAccessExpression) {
ExpressionTypingContext contextForResolve = context.replaceScope(scope).replaceBindingTrace(TemporaryBindingTrace.create(
context.trace, "trace to resolve array set method for assignment", expression));
basic.resolveImplicitArrayAccessSetMethod((KtArrayAccessExpression) left, right, contextForResolve, context.trace);
}
rightInfo = facade.getTypeInfo(right, context.replaceDataFlowInfo(leftInfo.getDataFlowInfo()));
KotlinType expectedType = refineTypeFromPropertySetterIfPossible(context.trace.getBindingContext(), leftOperand, leftType);
components.dataFlowAnalyzer.checkType(binaryOperationType, expression, context.replaceExpectedType(expectedType)
.replaceDataFlowInfo(rightInfo.getDataFlowInfo()).replaceCallPosition(new CallPosition.PropertyAssignment(left)));
basic.checkLValue(context.trace, context, leftOperand, right, expression);
}
temporary.commit();
return rightInfo.replaceType(checkAssignmentType(type, expression, contextWithExpectedType));
}
private static boolean atLeastOneOperation(Collection<? extends ResolvedCall<FunctionDescriptor>> calls, Name operationName) {
for (ResolvedCall<FunctionDescriptor> call : calls) {
if (call.getCandidateDescriptor().getName().equals(operationName)) {
return true;
}
}
return false;
}
@Nullable
private static KotlinType refineTypeFromPropertySetterIfPossible(
@NotNull BindingContext bindingContext,
@Nullable KtElement leftOperand,
@Nullable KotlinType leftOperandType
) {
VariableDescriptor descriptor = BindingContextUtils.extractVariableFromResolvedCall(bindingContext, leftOperand);
if (descriptor instanceof PropertyDescriptor) {
PropertySetterDescriptor setter = ((PropertyDescriptor) descriptor).getSetter();
if (setter != null) return setter.getValueParameters().get(0).getType();
}
return leftOperandType;
}
@NotNull
protected KotlinTypeInfo visitAssignment(KtBinaryExpression expression, ExpressionTypingContext contextWithExpectedType) {
ExpressionTypingContext context =
contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceScope(scope).replaceContextDependency(INDEPENDENT);
KtExpression leftOperand = expression.getLeft();
if (leftOperand instanceof KtAnnotatedExpression) {
basic.resolveAnnotationsOnExpression((KtAnnotatedExpression) leftOperand, context);
}
KtExpression left = deparenthesize(leftOperand);
KtExpression right = expression.getRight();
if (left instanceof KtArrayAccessExpression) {
KtArrayAccessExpression arrayAccessExpression = (KtArrayAccessExpression) left;
if (right == null) return TypeInfoFactoryKt.noTypeInfo(context);
KotlinTypeInfo typeInfo = basic.resolveArrayAccessSetMethod(arrayAccessExpression, right, context, context.trace);
basic.checkLValue(context.trace, context, arrayAccessExpression, right, expression);
return typeInfo.replaceType(checkAssignmentType(typeInfo.getType(), expression, contextWithExpectedType));
}
KotlinTypeInfo leftInfo = ExpressionTypingUtils.getTypeInfoOrNullType(left, context, facade);
KotlinType expectedType = refineTypeFromPropertySetterIfPossible(context.trace.getBindingContext(), leftOperand, leftInfo.getType());
DataFlowInfo dataFlowInfo = leftInfo.getDataFlowInfo();
KotlinTypeInfo resultInfo;
if (right != null) {
resultInfo = facade.getTypeInfo(
right, context.replaceDataFlowInfo(dataFlowInfo).replaceExpectedType(expectedType).replaceCallPosition(
new CallPosition.PropertyAssignment(leftOperand)));
dataFlowInfo = resultInfo.getDataFlowInfo();
KotlinType rightType = resultInfo.getType();
if (left != null && expectedType != null && rightType != null) {
DataFlowValue leftValue = DataFlowValueFactory.createDataFlowValue(left, expectedType, context);
DataFlowValue rightValue = DataFlowValueFactory.createDataFlowValue(right, rightType, context);
// We cannot say here anything new about rightValue except it has the same value as leftValue
resultInfo = resultInfo.replaceDataFlowInfo(dataFlowInfo.assign(leftValue, rightValue, components.languageVersionSettings));
}
}
else {
resultInfo = leftInfo;
}
if (expectedType != null && leftOperand != null) { //if expectedType == null, some other error has been generated
basic.checkLValue(context.trace, context, leftOperand, right, expression);
}
return resultInfo.replaceType(components.dataFlowAnalyzer.checkStatementType(expression, contextWithExpectedType));
}
@Override
public KotlinTypeInfo visitExpression(@NotNull KtExpression expression, ExpressionTypingContext context) {
return facade.getTypeInfo(expression, context);
}
@Override
public KotlinTypeInfo visitKtElement(@NotNull KtElement element, ExpressionTypingContext context) {
context.trace.report(UNSUPPORTED.on(element, "in a block"));
return TypeInfoFactoryKt.noTypeInfo(context);
}
@Override
public KotlinTypeInfo visitWhileExpression(@NotNull KtWhileExpression expression, ExpressionTypingContext context) {
return controlStructures.visitWhileExpression(expression, context, true);
}
@Override
public KotlinTypeInfo visitDoWhileExpression(@NotNull KtDoWhileExpression expression, ExpressionTypingContext context) {
return controlStructures.visitDoWhileExpression(expression, context, true);
}
@Override
public KotlinTypeInfo visitForExpression(@NotNull KtForExpression expression, ExpressionTypingContext context) {
return controlStructures.visitForExpression(expression, context, true);
}
@Override
public KotlinTypeInfo visitAnnotatedExpression(
@NotNull KtAnnotatedExpression expression, ExpressionTypingContext data
) {
return basic.visitAnnotatedExpression(expression, data, true);
}
@Override
public KotlinTypeInfo visitIfExpression(@NotNull KtIfExpression expression, ExpressionTypingContext context) {
return controlStructures.visitIfExpression(expression, context, true);
}
@Override
public KotlinTypeInfo visitWhenExpression(@NotNull KtWhenExpression expression, ExpressionTypingContext context) {
return patterns.visitWhenExpression(expression, context, true);
}
@Override
public KotlinTypeInfo visitBlockExpression(@NotNull KtBlockExpression expression, ExpressionTypingContext context) {
return components.expressionTypingServices.getBlockReturnedType(expression, context, true);
}
@Override
public KotlinTypeInfo visitLabeledExpression(@NotNull KtLabeledExpression expression, ExpressionTypingContext context) {
return basic.visitLabeledExpression(expression, context, true);
}
}