/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* 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 com.goide.psi.impl;
import com.goide.GoConstants;
import com.goide.psi.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
public class GoTypeUtil {
/**
* https://golang.org/ref/spec#For_statements
* The expression on the right in the "range" clause is called the range expression,
* which may be an array, pointer to an array, slice, string, map, or channel permitting receive operations.
*/
public static boolean isIterable(@Nullable GoType type) {
type = type != null ? type.getUnderlyingType() : null;
return type instanceof GoArrayOrSliceType ||
type instanceof GoPointerType && isArray(((GoPointerType)type).getType()) ||
type instanceof GoMapType ||
type instanceof GoChannelType ||
isString(type);
}
private static boolean isArray(@Nullable GoType type) {
type = type != null ? type.getUnderlyingType() : null;
return type instanceof GoArrayOrSliceType && ((GoArrayOrSliceType)type).getExpression() != null;
}
public static boolean isString(@Nullable GoType type) {
return isBuiltinType(type, "string");
}
public static boolean isBoolean(@Nullable GoType type) {
return isBuiltinType(type, "bool");
}
private static boolean isBuiltinType(@Nullable GoType type, @Nullable String builtinTypeName) {
if (builtinTypeName == null) return false;
type = type != null ? type.getUnderlyingType() : null;
return type != null &&
!(type instanceof GoCType) &&
type.textMatches(builtinTypeName) && GoPsiImplUtil.builtin(type);
}
@NotNull
public static List<GoType> getExpectedTypes(@NotNull GoExpression expression) {
PsiElement parent = expression.getParent();
if (parent == null) return Collections.emptyList();
if (parent instanceof GoAssignmentStatement) {
return getExpectedTypesFromAssignmentStatement(expression, (GoAssignmentStatement)parent);
}
if (parent instanceof GoRangeClause) {
return Collections.singletonList(getGoType(null, parent));
}
if (parent instanceof GoRecvStatement) {
return getExpectedTypesFromRecvStatement((GoRecvStatement)parent);
}
if (parent instanceof GoVarSpec) {
return getExpectedTypesFromVarSpec(expression, (GoVarSpec)parent);
}
if (parent instanceof GoArgumentList) {
return getExpectedTypesFromArgumentList(expression, (GoArgumentList)parent);
}
if (parent instanceof GoUnaryExpr) {
GoUnaryExpr unaryExpr = (GoUnaryExpr)parent;
if (unaryExpr.getSendChannel() != null) {
GoType type = ContainerUtil.getFirstItem(getExpectedTypes(unaryExpr));
GoType chanType = GoElementFactory.createType(parent.getProject(), "chan " + getInterfaceIfNull(type, parent).getText());
return Collections.singletonList(chanType);
}
else {
return Collections.singletonList(getGoType(null, parent));
}
}
if (parent instanceof GoSendStatement || parent instanceof GoLeftHandExprList && parent.getParent() instanceof GoSendStatement) {
GoSendStatement sendStatement = (GoSendStatement)(parent instanceof GoSendStatement ? parent : parent.getParent());
return getExpectedTypesFromGoSendStatement(expression, sendStatement);
}
if (parent instanceof GoExprCaseClause) {
return getExpectedTypesFromExprCaseClause((GoExprCaseClause)parent);
}
return Collections.emptyList();
}
@NotNull
private static List<GoType> getExpectedTypesFromExprCaseClause(@NotNull GoExprCaseClause exprCaseClause) {
GoExprSwitchStatement switchStatement = PsiTreeUtil.getParentOfType(exprCaseClause, GoExprSwitchStatement.class);
assert switchStatement != null;
GoExpression switchExpr = switchStatement.getExpression();
if (switchExpr != null) {
return Collections.singletonList(getGoType(switchExpr, exprCaseClause));
}
GoStatement statement = switchStatement.getStatement();
if (statement == null) {
return Collections.singletonList(getInterfaceIfNull(GoPsiImplUtil.getBuiltinType("bool", exprCaseClause), exprCaseClause));
}
GoLeftHandExprList leftHandExprList = statement instanceof GoSimpleStatement ? ((GoSimpleStatement)statement).getLeftHandExprList() : null;
GoExpression expr = leftHandExprList != null ? ContainerUtil.getFirstItem(leftHandExprList.getExpressionList()) : null;
return Collections.singletonList(getGoType(expr, exprCaseClause));
}
@NotNull
private static List<GoType> getExpectedTypesFromGoSendStatement(@NotNull GoExpression expression, @NotNull GoSendStatement statement) {
GoLeftHandExprList leftHandExprList = statement.getLeftHandExprList();
GoExpression channel = ContainerUtil.getFirstItem(leftHandExprList != null ? leftHandExprList.getExpressionList() : statement.getExpressionList());
GoExpression sendExpr = statement.getSendExpression();
assert channel != null;
if (expression.isEquivalentTo(sendExpr)) {
GoType chanType = channel.getGoType(null);
if (chanType instanceof GoChannelType) {
return Collections.singletonList(getInterfaceIfNull(((GoChannelType)chanType).getType(), statement));
}
}
if (expression.isEquivalentTo(channel)) {
GoType type = sendExpr != null ? sendExpr.getGoType(null) : null;
GoType chanType = GoElementFactory.createType(statement.getProject(), "chan " + getInterfaceIfNull(type, statement).getText());
return Collections.singletonList(chanType);
}
return Collections.singletonList(getInterfaceIfNull(null, statement));
}
@NotNull
private static List<GoType> getExpectedTypesFromArgumentList(@NotNull GoExpression expression, @NotNull GoArgumentList argumentList) {
PsiElement parentOfParent = argumentList.getParent();
assert parentOfParent instanceof GoCallExpr;
PsiReference reference = ((GoCallExpr)parentOfParent).getExpression().getReference();
if (reference != null) {
PsiElement resolve = reference.resolve();
if (resolve instanceof GoFunctionOrMethodDeclaration) {
GoSignature signature = ((GoFunctionOrMethodDeclaration)resolve).getSignature();
if (signature != null) {
List<GoExpression> exprList = argumentList.getExpressionList();
List<GoParameterDeclaration> paramsList = signature.getParameters().getParameterDeclarationList();
if (exprList.size() == 1) {
List<GoType> typeList = ContainerUtil.newSmartList();
for (GoParameterDeclaration parameterDecl : paramsList) {
for (GoParamDefinition parameter : parameterDecl.getParamDefinitionList()) {
typeList.add(getGoType(parameter, argumentList));
}
if (parameterDecl.getParamDefinitionList().isEmpty()) {
typeList.add(getInterfaceIfNull(parameterDecl.getType(), argumentList));
}
}
List<GoType> result = ContainerUtil.newSmartList(createGoTypeListOrGoType(typeList, argumentList));
if (paramsList.size() > 1) {
assert paramsList.get(0) != null;
result.add(getInterfaceIfNull(paramsList.get(0).getType(), argumentList));
}
return result;
}
else {
int position = exprList.indexOf(expression);
if (position >= 0) {
int i = 0;
for (GoParameterDeclaration parameterDecl : paramsList) {
int paramDeclSize = Math.max(1, parameterDecl.getParamDefinitionList().size());
if (i + paramDeclSize > position) {
return Collections.singletonList(getInterfaceIfNull(parameterDecl.getType(), argumentList));
}
i += paramDeclSize;
}
}
}
}
}
}
return Collections.singletonList(getInterfaceIfNull(null, argumentList));
}
@NotNull
private static List<GoType> getExpectedTypesFromRecvStatement(@NotNull GoRecvStatement recvStatement) {
List<GoType> typeList = ContainerUtil.newSmartList();
for (GoExpression expr : recvStatement.getLeftExpressionsList()) {
typeList.add(getGoType(expr, recvStatement));
}
return Collections.singletonList(createGoTypeListOrGoType(typeList, recvStatement));
}
@NotNull
private static List<GoType> getExpectedTypesFromVarSpec(@NotNull GoExpression expression, @NotNull GoVarSpec varSpec) {
List<GoType> result = ContainerUtil.newSmartList();
GoType type = getInterfaceIfNull(varSpec.getType(), varSpec);
if (varSpec.getRightExpressionsList().size() == 1) {
List<GoType> typeList = ContainerUtil.newSmartList();
int defListSize = varSpec.getVarDefinitionList().size();
for (int i = 0; i < defListSize; i++) {
typeList.add(type);
}
result.add(createGoTypeListOrGoType(typeList, expression));
if (defListSize > 1) {
result.add(getInterfaceIfNull(type, varSpec));
}
return result;
}
result.add(type);
return result;
}
@NotNull
private static List<GoType> getExpectedTypesFromAssignmentStatement(@NotNull GoExpression expression,
@NotNull GoAssignmentStatement assignment) {
List<GoExpression> leftExpressions = assignment.getLeftHandExprList().getExpressionList();
if (assignment.getExpressionList().size() == 1) {
List<GoType> typeList = ContainerUtil.newSmartList();
for (GoExpression expr : leftExpressions) {
GoType type = expr.getGoType(null);
typeList.add(type);
}
List<GoType> result = ContainerUtil.newSmartList(createGoTypeListOrGoType(typeList, expression));
if (leftExpressions.size() > 1) {
result.add(getGoType(leftExpressions.get(0), assignment));
}
return result;
}
int position = assignment.getExpressionList().indexOf(expression);
GoType leftExpression = leftExpressions.size() > position ? leftExpressions.get(position).getGoType(null) : null;
return Collections.singletonList(getInterfaceIfNull(leftExpression, assignment));
}
@NotNull
private static GoType createGoTypeListOrGoType(@NotNull List<GoType> types, @NotNull PsiElement context) {
if (types.size() < 2) {
return getInterfaceIfNull(ContainerUtil.getFirstItem(types), context);
}
return GoElementFactory.createTypeList(context.getProject(), StringUtil.join(types, type -> type == null ? GoConstants.INTERFACE_TYPE : type.getText(), ", "));
}
@NotNull
private static GoType getInterfaceIfNull(@Nullable GoType type, @NotNull PsiElement context) {
return type == null ? GoElementFactory.createType(context.getProject(), GoConstants.INTERFACE_TYPE) : type;
}
@NotNull
private static GoType getGoType(@Nullable GoTypeOwner element, @NotNull PsiElement context) {
return getInterfaceIfNull(element != null ? element.getGoType(null) : null, context);
}
public static boolean isFunction(@Nullable GoType goType) {
return goType != null && goType.getUnderlyingType() instanceof GoFunctionType;
}
}