/*
* 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.GoTypes;
import com.goide.psi.*;
import com.goide.psi.impl.imports.GoImportReferenceSet;
import com.goide.runconfig.testing.GoTestFinder;
import com.goide.sdk.GoPackageUtil;
import com.goide.sdk.GoSdkUtil;
import com.goide.stubs.*;
import com.goide.stubs.index.GoIdFilter;
import com.goide.stubs.index.GoMethodIndex;
import com.goide.util.GoStringLiteralEscaper;
import com.goide.util.GoUtil;
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
import com.intellij.diagnostic.AttachmentFactory;
import com.intellij.lang.ASTNode;
import com.intellij.lang.parser.GeneratedParserUtilBase;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceOwner;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.PsiFileReference;
import com.intellij.psi.impl.source.tree.LeafElement;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PathUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.goide.psi.impl.GoLightType.*;
import static com.intellij.codeInsight.highlighting.ReadWriteAccessDetector.Access.*;
import static com.intellij.openapi.util.Conditions.equalTo;
public class GoPsiImplUtil {
private static final Logger LOG = Logger.getInstance(GoPsiImplUtil.class);
private static final Key<SmartPsiElementPointer<PsiElement>> CONTEXT = Key.create("CONTEXT");
@NotNull
public static SyntaxTraverser<PsiElement> goTraverser() {
return SyntaxTraverser.psiTraverser().forceDisregardTypes(equalTo(GeneratedParserUtilBase.DUMMY_BLOCK));
}
public static boolean builtin(@Nullable PsiElement resolve) {
return resolve != null && isBuiltinFile(resolve.getContainingFile());
}
public static boolean isConversionExpression(@Nullable GoExpression expression) {
if (expression instanceof GoConversionExpr) {
return true;
}
GoReferenceExpression referenceExpression = null;
if (expression instanceof GoCallExpr) {
referenceExpression = ObjectUtils.tryCast(((GoCallExpr)expression).getExpression(), GoReferenceExpression.class);
}
else if (expression instanceof GoBuiltinCallExpr) {
referenceExpression = ((GoBuiltinCallExpr)expression).getReferenceExpression();
}
return referenceExpression != null && referenceExpression.resolve() instanceof GoTypeSpec;
}
public static boolean isPanic(@NotNull GoCallExpr o) {
return stdLibCall(o, "panic");
}
public static boolean isRecover(@NotNull GoCallExpr o) {
return stdLibCall(o, "recover");
}
private static boolean stdLibCall(@NotNull GoCallExpr o, @NotNull String name) {
GoExpression e = o.getExpression();
if (e.textMatches(name) && e instanceof GoReferenceExpression) {
PsiReference reference = e.getReference();
PsiElement resolve = reference != null ? reference.resolve() : null;
if (!(resolve instanceof GoFunctionDeclaration)) return false;
return isBuiltinFile(resolve.getContainingFile());
}
return false;
}
public static boolean isBuiltinFile(@Nullable PsiFile file) {
return file instanceof GoFile
&& GoConstants.BUILTIN_PACKAGE_NAME.equals(((GoFile)file).getPackageName())
&& GoConstants.BUILTIN_PACKAGE_NAME.equals(((GoFile)file).getImportPath(false))
&& GoConstants.BUILTIN_FILE_NAME.equals(file.getName());
}
@Nullable
public static GoTopLevelDeclaration getTopLevelDeclaration(@Nullable PsiElement startElement) {
GoTopLevelDeclaration declaration = PsiTreeUtil.getTopmostParentOfType(startElement, GoTopLevelDeclaration.class);
if (declaration == null || !(declaration.getParent() instanceof GoFile)) return null;
return declaration;
}
public static int getArity(@Nullable GoSignature s) {
return s == null ? -1 : s.getParameters().getParameterDeclarationList().size();
}
@Nullable
public static PsiElement getContextElement(@Nullable ResolveState state) {
SmartPsiElementPointer<PsiElement> context = state != null ? state.get(CONTEXT) : null;
return context != null ? context.getElement() : null;
}
@NotNull
public static ResolveState createContextOnElement(@NotNull PsiElement element) {
return ResolveState.initial().put(CONTEXT, SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element));
}
@Nullable
public static GoTypeReferenceExpression getQualifier(@NotNull GoTypeReferenceExpression o) {
return PsiTreeUtil.getChildOfType(o, GoTypeReferenceExpression.class);
}
@Nullable
public static PsiDirectory resolve(@NotNull GoImportString importString) {
PsiReference[] references = importString.getReferences();
for (PsiReference reference : references) {
if (reference instanceof FileReferenceOwner) {
PsiFileReference lastFileReference = ((FileReferenceOwner)reference).getLastFileReference();
PsiElement result = lastFileReference != null ? lastFileReference.resolve() : null;
return result instanceof PsiDirectory ? (PsiDirectory)result : null;
}
}
return null;
}
@NotNull
public static PsiReference getReference(@NotNull GoTypeReferenceExpression o) {
return new GoTypeReference(o);
}
@NotNull
public static PsiReference getReference(@NotNull GoLabelRef o) {
return new GoLabelReference(o);
}
@Nullable
public static PsiReference getReference(@NotNull GoVarDefinition o) {
GoShortVarDeclaration shortDeclaration = PsiTreeUtil.getParentOfType(o, GoShortVarDeclaration.class);
boolean createRef = PsiTreeUtil.getParentOfType(shortDeclaration, GoBlock.class, GoForStatement.class, GoIfStatement.class,
GoSwitchStatement.class, GoSelectStatement.class) instanceof GoBlock;
return createRef ? new GoVarReference(o) : null;
}
@NotNull
public static GoReference getReference(@NotNull GoReferenceExpression o) {
return new GoReference(o);
}
@NotNull
public static PsiReference getReference(@NotNull GoFieldName o) {
GoFieldNameReference field = new GoFieldNameReference(o);
GoReference ordinal = new GoReference(o);
return new PsiMultiReference(new PsiReference[]{field, ordinal}, o) {
@Override
public PsiElement resolve() {
PsiElement resolve = field.resolve();
return resolve != null ? resolve : field.inStructTypeKey() ? null : ordinal.resolve();
}
@Override
public boolean isReferenceTo(PsiElement element) {
return GoUtil.couldBeReferenceTo(element, getElement()) && getElement().getManager().areElementsEquivalent(resolve(), element);
}
};
}
@Nullable
public static GoReferenceExpression getQualifier(@SuppressWarnings("UnusedParameters") @NotNull GoFieldName o) {
return null;
}
@NotNull
public static PsiReference[] getReferences(@NotNull GoImportString o) {
if (o.getTextLength() < 2) return PsiReference.EMPTY_ARRAY;
return new GoImportReferenceSet(o).getAllReferences();
}
@Nullable
public static GoReferenceExpression getQualifier(@NotNull GoReferenceExpression o) {
return PsiTreeUtil.getChildOfType(o, GoReferenceExpression.class);
}
public static boolean processDeclarations(@NotNull GoCompositeElement o,
@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
boolean isAncestor = PsiTreeUtil.isAncestor(o, place, false);
if (o instanceof GoVarSpec) {
return isAncestor || GoCompositeElementImpl.processDeclarationsDefault(o, processor, state, lastParent, place);
}
if (isAncestor) return GoCompositeElementImpl.processDeclarationsDefault(o, processor, state, lastParent, place);
if (o instanceof GoBlock ||
o instanceof GoIfStatement ||
o instanceof GoForStatement ||
o instanceof GoCommClause ||
o instanceof GoFunctionLit ||
o instanceof GoTypeCaseClause ||
o instanceof GoExprCaseClause) {
return processor.execute(o, state);
}
return GoCompositeElementImpl.processDeclarationsDefault(o, processor, state, lastParent, place);
}
@Nullable
public static GoType getGoTypeInner(@NotNull GoReceiver o, @SuppressWarnings("UnusedParameters") @Nullable ResolveState context) {
return o.getType();
}
@Nullable
public static PsiElement getIdentifier(@SuppressWarnings("UnusedParameters") @NotNull GoAnonymousFieldDefinition o) {
GoTypeReferenceExpression expression = o.getTypeReferenceExpression();
return expression != null ? expression.getIdentifier() : null;
}
@Nullable
public static String getName(@NotNull GoPackageClause packageClause) {
GoPackageClauseStub stub = packageClause.getStub();
if (stub != null) return stub.getName();
PsiElement packageIdentifier = packageClause.getIdentifier();
return packageIdentifier != null ? packageIdentifier.getText().trim() : null;
}
@Nullable
public static String getName(@NotNull GoAnonymousFieldDefinition o) {
PsiElement identifier = o.getIdentifier();
return identifier != null ? identifier.getText() : null;
}
@Nullable
public static GoTypeReferenceExpression getTypeReferenceExpression(@NotNull GoAnonymousFieldDefinition o) {
return getTypeRefExpression(o.getType());
}
@Nullable
public static GoType getGoTypeInner(@NotNull GoAnonymousFieldDefinition o,
@SuppressWarnings("UnusedParameters") @Nullable ResolveState context) {
return o.getType();
}
@Nullable
public static String getName(@NotNull GoMethodSpec o) {
GoNamedStub<GoMethodSpec> stub = o.getStub();
if (stub != null) {
return stub.getName();
}
PsiElement identifier = o.getIdentifier();
if (identifier != null) return identifier.getText();
GoTypeReferenceExpression typeRef = o.getTypeReferenceExpression();
return typeRef != null ? typeRef.getIdentifier().getText() : null;
}
@Nullable
public static GoType getReceiverType(@NotNull GoMethodDeclaration o) {
GoReceiver receiver = o.getReceiver();
return receiver == null ? null : receiver.getType();
}
// todo: merge with {@link this#getTypeRefExpression}
@Nullable
public static GoTypeReferenceExpression getTypeReference(@Nullable GoType o) {
if (o instanceof GoPointerType) {
return PsiTreeUtil.findChildOfAnyType(o, GoTypeReferenceExpression.class);
}
return o != null ? o.getTypeReferenceExpression() : null;
}
// todo: merge with {@link this#getTypeReference}
@Nullable
public static GoTypeReferenceExpression getTypeRefExpression(@Nullable GoType type) {
GoType unwrap = unwrapPointerIfNeeded(type);
return unwrap != null ? unwrap.getTypeReferenceExpression() : null;
}
@Nullable
public static GoType getGoTypeInner(@NotNull GoConstDefinition o, @Nullable ResolveState context) {
GoType fromSpec = findTypeInConstSpec(o);
if (fromSpec != null) return fromSpec;
// todo: stubs
return RecursionManager.doPreventingRecursion(o, true, (NullableComputable<GoType>)() -> {
GoConstSpec prev = PsiTreeUtil.getPrevSiblingOfType(o.getParent(), GoConstSpec.class);
while (prev != null) {
GoType type = prev.getType();
if (type != null) return type;
GoExpression expr = ContainerUtil.getFirstItem(prev.getExpressionList()); // not sure about first
if (expr != null) return expr.getGoType(context);
prev = PsiTreeUtil.getPrevSiblingOfType(prev, GoConstSpec.class);
}
return null;
});
}
@Nullable
private static GoType findTypeInConstSpec(@NotNull GoConstDefinition o) {
GoConstDefinitionStub stub = o.getStub();
PsiElement parent = PsiTreeUtil.getStubOrPsiParent(o);
if (!(parent instanceof GoConstSpec)) return null;
GoConstSpec spec = (GoConstSpec)parent;
GoType commonType = spec.getType();
if (commonType != null) return commonType;
List<GoConstDefinition> varList = spec.getConstDefinitionList();
int i = Math.max(varList.indexOf(o), 0);
if (stub != null) return null;
GoConstSpecStub specStub = spec.getStub();
List<GoExpression> es = specStub != null ? specStub.getExpressionList() : spec.getExpressionList(); // todo: move to constant spec
if (es.size() <= i) return null;
return es.get(i).getGoType(null);
}
@Nullable
private static GoType unwrapParType(@NotNull GoExpression o, @Nullable ResolveState c) {
GoType inner = getGoTypeInner(o, c);
return inner instanceof GoParType ? ((GoParType)inner).getActualType() : inner;
}
@Nullable
public static GoType getGoType(@NotNull GoExpression o, @Nullable ResolveState context) {
return RecursionManager.doPreventingRecursion(o, true, () -> {
if (context != null) return unwrapParType(o, context);
return CachedValuesManager.getCachedValue(o, () -> CachedValueProvider.Result
.create(unwrapParType(o, createContextOnElement(o)), PsiModificationTracker.MODIFICATION_COUNT));
});
}
@Nullable
private static GoType getGoTypeInner(@NotNull GoExpression o, @Nullable ResolveState context) {
if (o instanceof GoUnaryExpr) {
GoUnaryExpr u = (GoUnaryExpr)o;
GoExpression e = u.getExpression();
if (e == null) return null;
GoType type = e.getGoType(context);
GoType base = type == null || type.getTypeReferenceExpression() == null ? type : type.getUnderlyingType();
if (u.getBitAnd() != null) return type != null ? new LightPointerType(type) : null;
if (u.getSendChannel() != null) return base instanceof GoChannelType ? ((GoChannelType)base).getType() : type;
if (u.getMul() != null) return base instanceof GoPointerType ? ((GoPointerType)base).getType() : type;
return type;
}
if (o instanceof GoAddExpr) {
return ((GoAddExpr)o).getLeft().getGoType(context);
}
if (o instanceof GoMulExpr) {
GoExpression left = ((GoMulExpr)o).getLeft();
if (!(left instanceof GoLiteral)) return left.getGoType(context);
GoExpression right = ((GoBinaryExpr)o).getRight();
if (right != null) return right.getGoType(context);
}
else if (o instanceof GoCompositeLit) {
GoType type = ((GoCompositeLit)o).getType();
if (type != null) return type;
GoTypeReferenceExpression expression = ((GoCompositeLit)o).getTypeReferenceExpression();
return expression != null ? expression.resolveType() : null;
}
else if (o instanceof GoFunctionLit) {
return new LightFunctionType((GoFunctionLit)o);
}
else if (o instanceof GoBuiltinCallExpr) {
String text = ((GoBuiltinCallExpr)o).getReferenceExpression().getText();
boolean isNew = "new".equals(text);
boolean isMake = "make".equals(text);
if (isNew || isMake) {
GoBuiltinArgumentList args = ((GoBuiltinCallExpr)o).getBuiltinArgumentList();
GoType type = args != null ? args.getType() : null;
return isNew ? type == null ? null : new LightPointerType(type) : type;
}
}
else if (o instanceof GoCallExpr) {
GoExpression e = ((GoCallExpr)o).getExpression();
if (e instanceof GoReferenceExpression) { // todo: unify Type processing
PsiReference ref = e.getReference();
PsiElement resolve = ref != null ? ref.resolve() : null;
if (((GoReferenceExpression)e).getQualifier() == null && "append".equals(((GoReferenceExpression)e).getIdentifier().getText())) {
if (resolve instanceof GoFunctionDeclaration && isBuiltinFile(resolve.getContainingFile())) {
List<GoExpression> l = ((GoCallExpr)o).getArgumentList().getExpressionList();
GoExpression f = ContainerUtil.getFirstItem(l);
return f == null ? null : getGoType(f, context);
}
}
else if (resolve == e) { // C.call()
return new GoCType(e);
}
}
GoType type = ((GoCallExpr)o).getExpression().getGoType(context);
if (type instanceof GoFunctionType) {
return funcType(type);
}
GoType byRef = type != null && type.getTypeReferenceExpression() != null ? type.getUnderlyingType() : null;
if (byRef instanceof GoFunctionType) {
return funcType(byRef);
}
return type;
}
else if (o instanceof GoReferenceExpression) {
PsiReference reference = o.getReference();
PsiElement resolve = reference != null ? reference.resolve() : null;
if (resolve instanceof GoTypeOwner) return typeOrParameterType((GoTypeOwner)resolve, context);
}
else if (o instanceof GoParenthesesExpr) {
GoExpression expression = ((GoParenthesesExpr)o).getExpression();
return expression != null ? expression.getGoType(context) : null;
}
else if (o instanceof GoSelectorExpr) {
GoExpression item = ContainerUtil.getLastItem(((GoSelectorExpr)o).getExpressionList());
return item != null ? item.getGoType(context) : null;
}
else if (o instanceof GoIndexOrSliceExpr) {
GoType referenceType = getIndexedExpressionReferenceType((GoIndexOrSliceExpr)o, context);
if (o.getNode().findChildByType(GoTypes.COLON) != null) return referenceType; // means slice expression, todo: extract if needed
GoType type = referenceType != null ? referenceType.getUnderlyingType() : null;
if (type instanceof GoMapType) {
List<GoType> list = ((GoMapType)type).getTypeList();
if (list.size() == 2) {
return list.get(1);
}
}
else if (type instanceof GoArrayOrSliceType) {
return ((GoArrayOrSliceType)type).getType();
}
else if (GoTypeUtil.isString(type)) {
return getBuiltinType("byte", o);
}
}
else if (o instanceof GoTypeAssertionExpr) {
return ((GoTypeAssertionExpr)o).getType();
}
else if (o instanceof GoConversionExpr) {
return ((GoConversionExpr)o).getType();
}
else if (o instanceof GoStringLiteral) {
return getBuiltinType("string", o);
}
else if (o instanceof GoLiteral) {
GoLiteral l = (GoLiteral)o;
if (l.getChar() != null) return getBuiltinType("rune", o);
if (l.getInt() != null || l.getHex() != null || ((GoLiteral)o).getOct() != null) return getBuiltinType("int", o);
if (l.getFloat() != null) return getBuiltinType("float64", o);
if (l.getFloati() != null) return getBuiltinType("complex64", o);
if (l.getDecimali() != null) return getBuiltinType("complex128", o);
}
else if (o instanceof GoConditionalExpr) {
return getBuiltinType("bool", o);
}
return null;
}
@Nullable
public static GoType getIndexedExpressionReferenceType(@NotNull GoIndexOrSliceExpr o, @Nullable ResolveState context) {
GoExpression first = ContainerUtil.getFirstItem(o.getExpressionList());
// todo: calculate type for indexed expressions only
// https://golang.org/ref/spec#Index_expressions – a[x] is shorthand for (*a)[x]
return unwrapPointerIfNeeded(first != null ? first.getGoType(context) : null);
}
@Nullable
public static GoType unwrapPointerIfNeeded(@Nullable GoType type) {
return type instanceof GoPointerType ? ((GoPointerType)type).getType() : type;
}
@Nullable
public static GoType getBuiltinType(@NotNull String name, @NotNull PsiElement context) {
GoFile builtin = GoSdkUtil.findBuiltinFile(context);
if (builtin != null) {
GoTypeSpec spec = ContainerUtil.find(builtin.getTypes(), spec1 -> name.equals(spec1.getName()));
if (spec != null) {
return spec.getSpecType().getType(); // todo
}
}
return null;
}
@Nullable
private static GoType typeFromRefOrType(@Nullable GoType t) {
if (t == null) return null;
GoTypeReferenceExpression tr = t.getTypeReferenceExpression();
return tr != null ? tr.resolveType() : t;
}
@Nullable
public static GoType typeOrParameterType(@NotNull GoTypeOwner resolve, @Nullable ResolveState context) {
GoType type = resolve.getGoType(context);
if (resolve instanceof GoParamDefinition && ((GoParamDefinition)resolve).isVariadic()) {
return type == null ? null : new LightArrayType(type);
}
if (resolve instanceof GoSignatureOwner) {
return new LightFunctionType((GoSignatureOwner)resolve);
}
return type;
}
@Nullable
public static PsiElement resolve(@NotNull GoReferenceExpression o) { // todo: replace with default method in GoReferenceExpressionBase
return o.getReference().resolve();
}
@Nullable
public static PsiElement resolve(@NotNull GoTypeReferenceExpression o) { // todo: replace with default method in GoReferenceExpressionBase
return o.getReference().resolve();
}
@Nullable
public static PsiElement resolve(@NotNull GoFieldName o) { // todo: replace with default method in GoReferenceExpressionBase
return o.getReference().resolve();
}
@Nullable
public static GoType getLiteralType(@Nullable PsiElement context, boolean considerLiteralValue) {
GoCompositeLit lit = PsiTreeUtil.getNonStrictParentOfType(context, GoCompositeLit.class);
if (lit == null) {
return null;
}
GoType type = lit.getType();
if (type == null) {
GoTypeReferenceExpression ref = lit.getTypeReferenceExpression();
GoType resolve = ref != null ? ref.resolveType() : null;
type = resolve != null ? resolve.getUnderlyingType() : null;
}
if (!considerLiteralValue) {
return type;
}
GoValue parentGoValue = getParentGoValue(context);
PsiElement literalValue = PsiTreeUtil.getParentOfType(context, GoLiteralValue.class);
while (literalValue != null) {
if (literalValue == lit) break;
if (literalValue instanceof GoLiteralValue) {
type = calcLiteralType(parentGoValue, type);
}
literalValue = literalValue.getParent();
}
return type;
}
@Nullable
public static GoValue getParentGoValue(@NotNull PsiElement element) {
PsiElement place = element;
while ((place = PsiTreeUtil.getParentOfType(place, GoLiteralValue.class)) != null) {
if (place.getParent() instanceof GoValue) {
return (GoValue)place.getParent();
}
}
return null;
}
// todo: rethink and unify this algorithm
@Nullable
private static GoType calcLiteralType(@Nullable GoValue parentGoValue, @Nullable GoType type) {
if (type == null) return null;
type = findLiteralType(parentGoValue, type);
if (type instanceof GoParType) {
type = ((GoParType)type).getActualType();
}
if (type != null && type.getTypeReferenceExpression() != null) {
type = type.getUnderlyingType();
}
if (type instanceof GoPointerType) {
GoType inner = ((GoPointerType)type).getType();
if (inner != null && inner.getTypeReferenceExpression() != null) {
type = inner.getUnderlyingType();
}
}
return type instanceof GoSpecType ? ((GoSpecType)type).getType() : type;
}
private static GoType findLiteralType(@Nullable GoValue parentGoValue, @Nullable GoType type) {
boolean inValue = parentGoValue != null;
if (inValue && type instanceof GoArrayOrSliceType) {
type = ((GoArrayOrSliceType)type).getType();
}
else if (type instanceof GoMapType) {
type = inValue ? ((GoMapType)type).getValueType() : ((GoMapType)type).getKeyType();
}
else if (inValue && type instanceof GoSpecType) {
GoType inner = ((GoSpecType)type).getType();
if (inner instanceof GoArrayOrSliceType) {
type = ((GoArrayOrSliceType)inner).getType();
}
else if (inner instanceof GoStructType) {
GoKey key = PsiTreeUtil.getPrevSiblingOfType(parentGoValue, GoKey.class);
GoFieldName field = key != null ? key.getFieldName() : null;
PsiElement resolve = field != null ? field.resolve() : null;
if (resolve instanceof GoFieldDefinition) {
type = PsiTreeUtil.getNextSiblingOfType(resolve, GoType.class);
}
}
}
return type;
}
@Nullable
public static GoType resolveType(@NotNull GoTypeReferenceExpression expression) {
PsiElement resolve = expression.resolve();
if (resolve instanceof GoTypeSpec) return ((GoTypeSpec)resolve).getSpecType();
// hacky C resolve
return resolve == expression ? new GoCType(expression) : null;
}
public static boolean isVariadic(@NotNull GoParamDefinition o) {
PsiElement parent = o.getParent();
return parent instanceof GoParameterDeclaration && ((GoParameterDeclaration)parent).isVariadic();
}
public static boolean isVariadic(@NotNull GoParameterDeclaration o) {
GoParameterDeclarationStub stub = o.getStub();
return stub != null ? stub.isVariadic() : o.getTripleDot() != null;
}
public static boolean hasVariadic(@NotNull GoArgumentList argumentList) {
return argumentList.getTripleDot() != null;
}
@Nullable
public static GoType getGoTypeInner(@NotNull GoTypeSpec o, @SuppressWarnings("UnusedParameters") @Nullable ResolveState context) {
return o.getSpecType();
}
@Nullable
public static GoType getGoTypeInner(@NotNull GoVarDefinition o, @Nullable ResolveState context) {
// see http://golang.org/ref/spec#RangeClause
PsiElement parent = PsiTreeUtil.getStubOrPsiParent(o);
if (parent instanceof GoRangeClause) {
return processRangeClause(o, (GoRangeClause)parent, context);
}
if (parent instanceof GoVarSpec) {
return findTypeInVarSpec(o, context);
}
GoCompositeLit literal = PsiTreeUtil.getNextSiblingOfType(o, GoCompositeLit.class);
if (literal != null) {
return literal.getType();
}
GoType siblingType = o.findSiblingType();
if (siblingType != null) return siblingType;
if (parent instanceof GoTypeSwitchGuard) {
GoTypeSwitchStatement switchStatement = ObjectUtils.tryCast(parent.getParent(), GoTypeSwitchStatement.class);
if (switchStatement != null) {
GoTypeCaseClause typeCase = getTypeCaseClause(getContextElement(context), switchStatement);
if (typeCase != null) {
return typeCase.getDefault() != null ? ((GoTypeSwitchGuard)parent).getExpression().getGoType(context) : typeCase.getType();
}
return ((GoTypeSwitchGuard)parent).getExpression().getGoType(null);
}
}
return null;
}
public static boolean isVoid(@NotNull GoResult result) {
GoType type = result.getType();
if (type != null) return false;
GoParameters parameters = result.getParameters();
return parameters == null || parameters.getParameterDeclarationList().isEmpty();
}
@Nullable
private static GoTypeCaseClause getTypeCaseClause(@Nullable PsiElement context, @NotNull GoTypeSwitchStatement switchStatement) {
return SyntaxTraverser.psiApi().parents(context).takeWhile(Conditions.notEqualTo(switchStatement))
.filter(GoTypeCaseClause.class).last();
}
@Nullable
private static GoType findTypeInVarSpec(@NotNull GoVarDefinition o, @Nullable ResolveState context) {
GoVarSpec parent = (GoVarSpec)PsiTreeUtil.getStubOrPsiParent(o);
if (parent == null) return null;
GoType commonType = parent.getType();
if (commonType != null) return commonType;
List<GoVarDefinition> varList = parent.getVarDefinitionList();
int i = varList.indexOf(o);
i = i == -1 ? 0 : i;
List<GoExpression> exprs = parent.getRightExpressionsList();
if (exprs.size() == 1 && exprs.get(0) instanceof GoCallExpr) {
GoExpression call = exprs.get(0);
GoType fromCall = call.getGoType(context);
boolean canDecouple = varList.size() > 1;
GoType underlyingType = canDecouple && fromCall instanceof GoSpecType ? ((GoSpecType)fromCall).getType() : fromCall;
GoType byRef = typeFromRefOrType(underlyingType);
GoType type = funcType(canDecouple && byRef instanceof GoSpecType ? ((GoSpecType)byRef).getType() : byRef);
if (type == null) return fromCall;
if (type instanceof GoTypeList) {
if (((GoTypeList)type).getTypeList().size() > i) {
return ((GoTypeList)type).getTypeList().get(i);
}
}
return type;
}
if (exprs.size() <= i) return null;
return exprs.get(i).getGoType(context);
}
@Nullable
private static GoType funcType(@Nullable GoType type) {
if (type instanceof GoFunctionType) {
GoSignature signature = ((GoFunctionType)type).getSignature();
GoResult result = signature != null ? signature.getResult() : null;
if (result != null) {
GoType rt = result.getType();
if (rt != null) return rt;
GoParameters parameters = result.getParameters();
if (parameters != null) {
List<GoParameterDeclaration> list = parameters.getParameterDeclarationList();
List<GoType> types = ContainerUtil.newArrayListWithCapacity(list.size());
for (GoParameterDeclaration declaration : list) {
GoType declarationType = declaration.getType();
for (GoParamDefinition ignored : declaration.getParamDefinitionList()) {
types.add(declarationType);
}
}
if (!types.isEmpty()) {
return types.size() == 1 ? types.get(0) : new LightTypeList(parameters, types);
}
}
}
return null;
}
return type;
}
/**
* https://golang.org/ref/spec#RangeClause
*/
@Nullable
private static GoType processRangeClause(@NotNull GoVarDefinition o, @NotNull GoRangeClause parent, @Nullable ResolveState context) {
GoExpression rangeExpression = parent.getRangeExpression();
if (rangeExpression != null) {
List<GoVarDefinition> varList = parent.getVarDefinitionList();
GoType type = unwrapIfNeeded(rangeExpression.getGoType(context));
if (type instanceof GoChannelType) return ((GoChannelType)type).getType();
int i = varList.indexOf(o);
i = i == -1 ? 0 : i;
if (type instanceof GoArrayOrSliceType && i == 1) return ((GoArrayOrSliceType)type).getType();
if (type instanceof GoMapType) {
List<GoType> list = ((GoMapType)type).getTypeList();
if (i == 0) return ContainerUtil.getFirstItem(list);
if (i == 1) return ContainerUtil.getLastItem(list);
}
if (GoTypeUtil.isString(type)) {
return getBuiltinType("int32", o);
}
}
return null;
}
@Nullable
private static GoType unwrapIfNeeded(@Nullable GoType type) {
type = unwrapPointerIfNeeded(type);
return type != null ? type.getUnderlyingType() : null;
}
@NotNull
public static GoType getActualType(@NotNull GoParType o) {
return ObjectUtils.notNull(SyntaxTraverser.psiTraverser(o).filter(Conditions.notInstanceOf(GoParType.class))
.filter(GoType.class).first(), o.getType());
}
@NotNull
public static String getText(@Nullable GoType o) {
if (o == null) return "";
if (o instanceof GoPointerType && ((GoPointerType)o).getType() instanceof GoSpecType) {
return "*" + getText(((GoPointerType)o).getType());
}
if (o instanceof GoSpecType) {
String fqn = getFqn(getTypeSpecSafe(o));
if (fqn != null) {
return fqn;
}
}
else if (o instanceof GoStructType) {
return ((GoStructType)o).getFieldDeclarationList().isEmpty() ? "struct{}" : "struct {...}";
}
else if (o instanceof GoInterfaceType) {
return ((GoInterfaceType)o).getMethodSpecList().isEmpty() ? "interface{}" : "interface {...}";
}
String text = o.getText();
if (text == null) return "";
return text.replaceAll("\\s+", " ");
}
@Nullable
private static String getFqn(@Nullable GoTypeSpec typeSpec) {
if (typeSpec == null) return null;
String name = typeSpec.getName();
GoFile file = typeSpec.getContainingFile();
String packageName = file.getPackageName();
if (name != null) {
return !isBuiltinFile(file) ? getFqn(packageName, name) : name;
}
return null;
}
public static String getFqn(@Nullable String packageName, @NotNull String elementName) {
return StringUtil.isNotEmpty(packageName) ? packageName + "." + elementName : elementName;
}
@NotNull
public static List<GoMethodSpec> getMethods(@NotNull GoInterfaceType o) {
return ContainerUtil.filter(o.getMethodSpecList(), spec -> spec.getIdentifier() != null);
}
@NotNull
public static List<GoTypeReferenceExpression> getBaseTypesReferences(@NotNull GoInterfaceType o) {
List<GoTypeReferenceExpression> refs = ContainerUtil.newArrayList();
o.accept(new GoRecursiveVisitor() {
@Override
public void visitMethodSpec(@NotNull GoMethodSpec o) {
ContainerUtil.addIfNotNull(refs, o.getTypeReferenceExpression());
}
});
return refs;
}
@NotNull
public static List<GoMethodDeclaration> getMethods(@NotNull GoTypeSpec o) {
return CachedValuesManager.getCachedValue(o, () -> {
// todo[zolotov]: implement package modification tracker
return CachedValueProvider.Result.create(calcMethods(o), PsiModificationTracker.MODIFICATION_COUNT);
});
}
public static boolean allowed(@NotNull PsiFile declarationFile, @Nullable PsiFile referenceFile, @Nullable Module contextModule) {
if (!(declarationFile instanceof GoFile)) {
return false;
}
VirtualFile referenceVirtualFile = referenceFile != null ? referenceFile.getOriginalFile().getVirtualFile() : null;
if (!allowed(declarationFile.getVirtualFile(), referenceVirtualFile)) {
return false;
}
if (GoConstants.DOCUMENTATION.equals(((GoFile)declarationFile).getPackageName())) {
return false;
}
return GoUtil.matchedForModuleBuildTarget(declarationFile, contextModule);
}
public static boolean allowed(@Nullable VirtualFile declarationFile, @Nullable VirtualFile referenceFile) {
if (declarationFile == null) {
return true;
}
if (GoUtil.fileToIgnore(declarationFile.getName())) {
return false;
}
// it's not a test or context file is also test from the same package
return referenceFile == null
|| !GoTestFinder.isTestFile(declarationFile)
|| GoTestFinder.isTestFile(referenceFile) && Comparing.equal(referenceFile.getParent(), declarationFile.getParent());
}
static boolean processNamedElements(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@NotNull Collection<? extends GoNamedElement> elements,
boolean localResolve) {
//noinspection unchecked
return processNamedElements(processor, state, elements, Condition.TRUE, localResolve, false);
}
static boolean processNamedElements(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@NotNull Collection<? extends GoNamedElement> elements,
boolean localResolve,
boolean checkContainingFile) {
//noinspection unchecked
return processNamedElements(processor, state, elements, Condition.TRUE, localResolve, checkContainingFile);
}
static boolean processNamedElements(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@NotNull Collection<? extends GoNamedElement> elements,
@NotNull Condition<GoNamedElement> condition,
boolean localResolve,
boolean checkContainingFile) {
PsiFile contextFile = checkContainingFile ? GoReference.getContextFile(state) : null;
Module module = contextFile != null ? ModuleUtilCore.findModuleForPsiElement(contextFile) : null;
for (GoNamedElement definition : elements) {
if (!condition.value(definition)) continue;
if (!definition.isValid() || checkContainingFile && !allowed(definition.getContainingFile(), contextFile, module)) continue;
if ((localResolve || definition.isPublic()) && !processor.execute(definition, state)) return false;
}
return true;
}
public static boolean processSignatureOwner(@NotNull GoSignatureOwner o, @NotNull GoScopeProcessorBase processor) {
GoSignature signature = o.getSignature();
if (signature == null) return true;
if (!processParameters(processor, signature.getParameters())) return false;
GoResult result = signature.getResult();
GoParameters resultParameters = result != null ? result.getParameters() : null;
return resultParameters == null || processParameters(processor, resultParameters);
}
private static boolean processParameters(@NotNull GoScopeProcessorBase processor, @NotNull GoParameters parameters) {
for (GoParameterDeclaration declaration : parameters.getParameterDeclarationList()) {
if (!processNamedElements(processor, ResolveState.initial(), declaration.getParamDefinitionList(), true)) return false;
}
return true;
}
@NotNull
public static String joinPsiElementText(List<? extends PsiElement> items) {
return StringUtil.join(items, PsiElement::getText, ", ");
}
@Nullable
public static PsiElement getBreakStatementOwner(@NotNull PsiElement breakStatement) {
GoCompositeElement owner = PsiTreeUtil.getParentOfType(breakStatement, GoSwitchStatement.class, GoForStatement.class,
GoSelectStatement.class, GoFunctionLit.class);
return owner instanceof GoFunctionLit ? null : owner;
}
@NotNull
private static List<GoMethodDeclaration> calcMethods(@NotNull GoTypeSpec o) {
PsiFile file = o.getContainingFile().getOriginalFile();
if (file instanceof GoFile) {
String packageName = ((GoFile)file).getPackageName();
String typeName = o.getName();
if (StringUtil.isEmpty(packageName) || StringUtil.isEmpty(typeName)) return Collections.emptyList();
String key = packageName + "." + typeName;
Project project = ((GoFile)file).getProject();
GlobalSearchScope scope = GoPackageUtil.packageScope((GoFile)file);
Collection<GoMethodDeclaration> declarations = GoMethodIndex.find(key, project, scope, GoIdFilter.getFilesFilter(scope));
return ContainerUtil.newArrayList(declarations);
}
return Collections.emptyList();
}
@NotNull
public static GoType getUnderlyingType(@NotNull GoType o) {
GoType type = RecursionManager.doPreventingRecursion(o, true, () -> getTypeInner(o));
return ObjectUtils.notNull(type, o);
}
@NotNull
private static GoType getTypeInner(@NotNull GoType o) {
if (o instanceof GoArrayOrSliceType
| o instanceof GoStructType
| o instanceof GoPointerType
| o instanceof GoFunctionType
| o instanceof GoInterfaceType
| o instanceof GoMapType
| o instanceof GoChannelType) {
return o;
}
if (o instanceof GoParType) return ((GoParType)o).getActualType();
if (o instanceof GoSpecType) {
GoType type = ((GoSpecType)o).getType();
return type != null ? type.getUnderlyingType() : o;
}
if (builtin(o)) return o;
GoTypeReferenceExpression e = o.getTypeReferenceExpression();
GoType byRef = e == null ? null : e.resolveType();
if (byRef != null) {
return byRef.getUnderlyingType();
}
return o;
}
@Nullable
public static GoType getGoTypeInner(@NotNull GoSignatureOwner o, @SuppressWarnings("UnusedParameters") @Nullable ResolveState context) {
GoSignature signature = o.getSignature();
GoResult result = signature != null ? signature.getResult() : null;
if (result != null) {
GoType type = result.getType();
if (type instanceof GoTypeList && ((GoTypeList)type).getTypeList().size() == 1) {
return ((GoTypeList)type).getTypeList().get(0);
}
if (type != null) return type;
GoParameters parameters = result.getParameters();
if (parameters != null) {
GoType parametersType = parameters.getType();
if (parametersType != null) return parametersType;
List<GoType> composite = ContainerUtil.newArrayList();
for (GoParameterDeclaration p : parameters.getParameterDeclarationList()) {
for (GoParamDefinition definition : p.getParamDefinitionList()) {
composite.add(definition.getGoType(context));
}
}
if (composite.size() == 1) return composite.get(0);
return new LightTypeList(parameters, composite);
}
}
return null;
}
@NotNull
public static GoImportSpec addImport(@NotNull GoImportList importList, @NotNull String packagePath, @Nullable String alias) {
Project project = importList.getProject();
GoImportDeclaration newDeclaration = GoElementFactory.createImportDeclaration(project, packagePath, alias, false);
List<GoImportDeclaration> existingImports = importList.getImportDeclarationList();
for (int i = existingImports.size() - 1; i >= 0; i--) {
GoImportDeclaration existingImport = existingImports.get(i);
List<GoImportSpec> importSpecList = existingImport.getImportSpecList();
if (importSpecList.isEmpty()) {
continue;
}
if (existingImport.getRparen() == null && importSpecList.get(0).isCImport()) {
continue;
}
return existingImport.addImportSpec(packagePath, alias);
}
return addImportDeclaration(importList, newDeclaration);
}
@NotNull
private static GoImportSpec addImportDeclaration(@NotNull GoImportList importList, @NotNull GoImportDeclaration newImportDeclaration) {
GoImportDeclaration lastImport = ContainerUtil.getLastItem(importList.getImportDeclarationList());
GoImportDeclaration importDeclaration = (GoImportDeclaration)importList.addAfter(newImportDeclaration, lastImport);
PsiElement importListNextSibling = importList.getNextSibling();
if (!(importListNextSibling instanceof PsiWhiteSpace)) {
importList.addAfter(GoElementFactory.createNewLine(importList.getProject()), importDeclaration);
if (importListNextSibling != null) {
// double new line if there is something valuable after import list
importList.addAfter(GoElementFactory.createNewLine(importList.getProject()), importDeclaration);
}
}
importList.addBefore(GoElementFactory.createNewLine(importList.getProject()), importDeclaration);
GoImportSpec result = ContainerUtil.getFirstItem(importDeclaration.getImportSpecList());
assert result != null;
return result;
}
@NotNull
public static GoImportSpec addImportSpec(@NotNull GoImportDeclaration declaration, @NotNull String packagePath, @Nullable String alias) {
PsiElement rParen = declaration.getRparen();
if (rParen == null) {
GoImportDeclaration newDeclaration = GoElementFactory.createEmptyImportDeclaration(declaration.getProject());
for (GoImportSpec spec : declaration.getImportSpecList()) {
newDeclaration.addImportSpec(spec.getPath(), spec.getAlias());
}
declaration = (GoImportDeclaration)declaration.replace(newDeclaration);
LOG.assertTrue(declaration.getRparen() != null);
return declaration.addImportSpec(packagePath, alias);
}
declaration.addBefore(GoElementFactory.createNewLine(declaration.getProject()), rParen);
GoImportSpec newImportSpace = GoElementFactory.createImportSpec(declaration.getProject(), packagePath, alias);
GoImportSpec spec = (GoImportSpec)declaration.addBefore(newImportSpace, rParen);
declaration.addBefore(GoElementFactory.createNewLine(declaration.getProject()), rParen);
return spec;
}
public static String getLocalPackageName(@NotNull String importPath) {
String fileName = !StringUtil.endsWithChar(importPath, '/') && !StringUtil.endsWithChar(importPath, '\\')
? PathUtil.getFileName(importPath)
: "";
StringBuilder name = null;
for (int i = 0; i < fileName.length(); i++) {
char c = fileName.charAt(i);
if (!(Character.isLetter(c) || c == '_' || i != 0 && Character.isDigit(c))) {
if (name == null) {
name = new StringBuilder(fileName.length());
name.append(fileName, 0, i);
}
name.append('_');
}
else if (name != null) {
name.append(c);
}
}
return name == null ? fileName : name.toString();
}
public static String getLocalPackageName(@NotNull GoImportSpec importSpec) {
return getLocalPackageName(importSpec.getPath());
}
public static boolean isCImport(@NotNull GoImportSpec importSpec) {
return GoConstants.C_PATH.equals(importSpec.getPath());
}
public static boolean isDot(@NotNull GoImportSpec importSpec) {
GoImportSpecStub stub = importSpec.getStub();
return stub != null ? stub.isDot() : importSpec.getDot() != null;
}
@NotNull
public static String getPath(@NotNull GoImportSpec importSpec) {
GoImportSpecStub stub = importSpec.getStub();
return stub != null ? stub.getPath() : importSpec.getImportString().getPath();
}
public static String getName(@NotNull GoImportSpec importSpec) {
return getAlias(importSpec);
}
public static String getAlias(@NotNull GoImportSpec importSpec) {
GoImportSpecStub stub = importSpec.getStub();
if (stub != null) {
return stub.getAlias();
}
PsiElement identifier = importSpec.getIdentifier();
if (identifier != null) {
return identifier.getText();
}
return importSpec.isDot() ? "." : null;
}
@Nullable
public static String getImportQualifierToUseInFile(@Nullable GoImportSpec importSpec, @Nullable String defaultValue) {
if (importSpec == null || importSpec.isForSideEffects()) {
return null;
}
if (importSpec.isDot()) {
return "";
}
String alias = importSpec.getAlias();
if (alias != null) {
return alias;
}
return defaultValue != null ? defaultValue : importSpec.getLocalPackageName();
}
public static boolean shouldGoDeeper(@SuppressWarnings("UnusedParameters") GoImportSpec o) {
return false;
}
public static boolean shouldGoDeeper(@SuppressWarnings("UnusedParameters") GoTypeSpec o) {
return false;
}
public static boolean shouldGoDeeper(@NotNull GoType o) {
return o instanceof GoInterfaceType || o instanceof GoStructType;
}
public static boolean isForSideEffects(@NotNull GoImportSpec o) {
return "_".equals(o.getAlias());
}
@NotNull
public static String getPath(@NotNull GoImportString o) {
return o.getStringLiteral().getDecodedText();
}
@NotNull
public static String unquote(@Nullable String s) {
if (StringUtil.isEmpty(s)) return "";
char quote = s.charAt(0);
int startOffset = isQuote(quote) ? 1 : 0;
int endOffset = s.length();
if (s.length() > 1) {
char lastChar = s.charAt(s.length() - 1);
if (isQuote(quote) && lastChar == quote) {
endOffset = s.length() - 1;
}
if (!isQuote(quote) && isQuote(lastChar)) {
endOffset = s.length() - 1;
}
}
return s.substring(startOffset, endOffset);
}
@NotNull
public static TextRange getPathTextRange(@NotNull GoImportString importString) {
String text = importString.getText();
return !text.isEmpty() && isQuote(text.charAt(0)) ? TextRange.create(1, text.length() - 1) : TextRange.EMPTY_RANGE;
}
public static boolean isQuotedImportString(@NotNull String s) {
return s.length() > 1 && isQuote(s.charAt(0)) && s.charAt(0) == s.charAt(s.length() - 1);
}
private static boolean isQuote(char ch) {
return ch == '"' || ch == '\'' || ch == '`';
}
public static boolean isValidHost(@NotNull GoStringLiteral o) {
return PsiTreeUtil.getParentOfType(o, GoImportString.class) == null;
}
@NotNull
public static GoStringLiteralImpl updateText(@NotNull GoStringLiteral o, @NotNull String text) {
if (text.length() > 2) {
if (o.getString() != null) {
StringBuilder outChars = new StringBuilder();
GoStringLiteralEscaper.escapeString(text.substring(1, text.length() - 1), outChars);
outChars.insert(0, '"');
outChars.append('"');
text = outChars.toString();
}
}
ASTNode valueNode = o.getNode().getFirstChildNode();
assert valueNode instanceof LeafElement;
((LeafElement)valueNode).replaceWithText(text);
return (GoStringLiteralImpl)o;
}
@NotNull
public static GoStringLiteralEscaper createLiteralTextEscaper(@NotNull GoStringLiteral o) {
return new GoStringLiteralEscaper(o);
}
public static boolean prevDot(@Nullable PsiElement e) {
PsiElement prev = e == null ? null : PsiTreeUtil.prevVisibleLeaf(e);
return prev instanceof LeafElement && ((LeafElement)prev).getElementType() == GoTypes.DOT;
}
@Nullable
public static GoSignatureOwner resolveCall(@Nullable GoExpression call) {
return ObjectUtils.tryCast(resolveCallRaw(call), GoSignatureOwner.class);
}
public static PsiElement resolveCallRaw(@Nullable GoExpression call) {
if (!(call instanceof GoCallExpr)) return null;
GoExpression e = ((GoCallExpr)call).getExpression();
if (e instanceof GoSelectorExpr) {
GoExpression right = ((GoSelectorExpr)e).getRight();
PsiReference reference = right instanceof GoReferenceExpression ? right.getReference() : null;
return reference != null ? reference.resolve() : null;
}
if (e instanceof GoCallExpr) {
GoSignatureOwner resolve = resolveCall(e);
if (resolve != null) {
GoSignature signature = resolve.getSignature();
GoResult result = signature != null ? signature.getResult() : null;
return result != null ? result.getType() : null;
}
return null;
}
if (e instanceof GoFunctionLit) {
return e;
}
GoReferenceExpression r = e instanceof GoReferenceExpression
? (GoReferenceExpression)e
: PsiTreeUtil.getChildOfType(e, GoReferenceExpression.class);
PsiReference reference = (r != null ? r : e).getReference();
return reference != null ? reference.resolve() : null;
}
public static boolean isUnaryBitAndExpression(@Nullable PsiElement parent) {
PsiElement grandParent = parent != null ? parent.getParent() : null;
return grandParent instanceof GoUnaryExpr && ((GoUnaryExpr)grandParent).getBitAnd() != null;
}
@NotNull
public static GoVarSpec addSpec(@NotNull GoVarDeclaration declaration,
@NotNull String name,
@Nullable String type,
@Nullable String value,
@Nullable GoVarSpec specAnchor) {
Project project = declaration.getProject();
GoVarSpec newSpec = GoElementFactory.createVarSpec(project, name, type, value);
PsiElement rParen = declaration.getRparen();
if (rParen == null) {
GoVarSpec item = ContainerUtil.getFirstItem(declaration.getVarSpecList());
assert item != null;
boolean updateAnchor = specAnchor == item;
declaration = (GoVarDeclaration)declaration.replace(GoElementFactory.createVarDeclaration(project, "(" + item.getText() + ")"));
rParen = declaration.getRparen();
if (updateAnchor) {
specAnchor = ContainerUtil.getFirstItem(declaration.getVarSpecList());
}
}
assert rParen != null;
PsiElement anchor = ObjectUtils.notNull(specAnchor, rParen);
if (!hasNewLineBefore(anchor)) {
declaration.addBefore(GoElementFactory.createNewLine(declaration.getProject()), anchor);
}
GoVarSpec spec = (GoVarSpec)declaration.addBefore(newSpec, anchor);
declaration.addBefore(GoElementFactory.createNewLine(declaration.getProject()), anchor);
return spec;
}
@NotNull
public static GoConstSpec addSpec(@NotNull GoConstDeclaration declaration,
@NotNull String name,
@Nullable String type,
@Nullable String value,
@Nullable GoConstSpec specAnchor) {
Project project = declaration.getProject();
GoConstSpec newSpec = GoElementFactory.createConstSpec(project, name, type, value);
PsiElement rParen = declaration.getRparen();
if (rParen == null) {
GoConstSpec item = ContainerUtil.getFirstItem(declaration.getConstSpecList());
assert item != null;
boolean updateAnchor = specAnchor == item;
declaration = (GoConstDeclaration)declaration.replace(GoElementFactory.createConstDeclaration(project, "(" + item.getText() + ")"));
rParen = declaration.getRparen();
if (updateAnchor) {
specAnchor = ContainerUtil.getFirstItem(declaration.getConstSpecList());
}
}
assert rParen != null;
PsiElement anchor = ObjectUtils.notNull(specAnchor, rParen);
if (!hasNewLineBefore(anchor)) {
declaration.addBefore(GoElementFactory.createNewLine(declaration.getProject()), anchor);
}
GoConstSpec spec = (GoConstSpec)declaration.addBefore(newSpec, anchor);
declaration.addBefore(GoElementFactory.createNewLine(declaration.getProject()), anchor);
return spec;
}
public static void deleteSpec(@NotNull GoVarDeclaration declaration, @NotNull GoVarSpec specToDelete) {
List<GoVarSpec> specList = declaration.getVarSpecList();
int index = specList.indexOf(specToDelete);
assert index >= 0;
if (specList.size() == 1) {
declaration.delete();
return;
}
specToDelete.delete();
}
public static void deleteSpec(@NotNull GoConstDeclaration declaration, @NotNull GoConstSpec specToDelete) {
List<GoConstSpec> specList = declaration.getConstSpecList();
int index = specList.indexOf(specToDelete);
assert index >= 0;
if (specList.size() == 1) {
declaration.delete();
return;
}
specToDelete.delete();
}
public static void deleteExpressionFromAssignment(@NotNull GoAssignmentStatement assignment,
@NotNull GoExpression expressionToDelete) {
GoExpression expressionValue = getRightExpression(assignment, expressionToDelete);
if (expressionValue != null) {
if (assignment.getExpressionList().size() == 1) {
assignment.delete();
}
else {
deleteElementFromCommaSeparatedList(expressionToDelete);
deleteElementFromCommaSeparatedList(expressionValue);
}
}
}
public static void deleteDefinition(@NotNull GoVarSpec spec, @NotNull GoVarDefinition definitionToDelete) {
List<GoVarDefinition> definitionList = spec.getVarDefinitionList();
int index = definitionList.indexOf(definitionToDelete);
assert index >= 0;
if (definitionList.size() == 1) {
PsiElement parent = spec.getParent();
if (parent instanceof GoVarDeclaration) {
((GoVarDeclaration)parent).deleteSpec(spec);
}
else {
spec.delete();
}
return;
}
GoExpression value = definitionToDelete.getValue();
if (value != null && spec.getRightExpressionsList().size() <= 1) {
PsiElement assign = spec.getAssign();
if (assign != null) {
assign.delete();
}
}
deleteElementFromCommaSeparatedList(value);
deleteElementFromCommaSeparatedList(definitionToDelete);
}
public static void deleteDefinition(@NotNull GoConstSpec spec, @NotNull GoConstDefinition definitionToDelete) {
List<GoConstDefinition> definitionList = spec.getConstDefinitionList();
int index = definitionList.indexOf(definitionToDelete);
assert index >= 0;
if (definitionList.size() == 1) {
PsiElement parent = spec.getParent();
if (parent instanceof GoConstDeclaration) {
((GoConstDeclaration)parent).deleteSpec(spec);
}
else {
spec.delete();
}
return;
}
GoExpression value = definitionToDelete.getValue();
if (value != null && spec.getExpressionList().size() <= 1) {
PsiElement assign = spec.getAssign();
if (assign != null) {
assign.delete();
}
}
deleteElementFromCommaSeparatedList(value);
deleteElementFromCommaSeparatedList(definitionToDelete);
}
private static void deleteElementFromCommaSeparatedList(@Nullable PsiElement element) {
if (element == null) {
return;
}
PsiElement prevVisibleLeaf = PsiTreeUtil.prevVisibleLeaf(element);
PsiElement nextVisibleLeaf = PsiTreeUtil.nextVisibleLeaf(element);
if (prevVisibleLeaf != null && prevVisibleLeaf.textMatches(",")) {
prevVisibleLeaf.delete();
}
else if (nextVisibleLeaf != null && nextVisibleLeaf.textMatches(",")) {
nextVisibleLeaf.delete();
}
element.delete();
}
private static boolean hasNewLineBefore(@NotNull PsiElement anchor) {
PsiElement prevSibling = anchor.getPrevSibling();
while (prevSibling instanceof PsiWhiteSpace) {
if (prevSibling.textContains('\n')) {
return true;
}
prevSibling = prevSibling.getPrevSibling();
}
return false;
}
@Nullable
public static GoExpression getValue(@NotNull GoVarDefinition definition) {
PsiElement parent = definition.getParent();
if (parent instanceof GoVarSpec) {
int index = ((GoVarSpec)parent).getVarDefinitionList().indexOf(definition);
return getByIndex(((GoVarSpec)parent).getRightExpressionsList(), index);
}
if (parent instanceof GoTypeSwitchGuard) {
return ((GoTypeSwitchGuard)parent).getExpression();
}
LOG.error("Cannot find value for variable definition: " + definition.getText(),
AttachmentFactory.createAttachment(definition.getContainingFile().getVirtualFile()));
return null;
}
@Nullable
public static GoExpression getValue(@NotNull GoConstDefinition definition) {
PsiElement parent = definition.getParent();
assert parent instanceof GoConstSpec;
int index = ((GoConstSpec)parent).getConstDefinitionList().indexOf(definition);
return getByIndex(((GoConstSpec)parent).getExpressionList(), index);
}
private static <T> T getByIndex(@NotNull List<T> list, int index) {
return 0 <= index && index < list.size() ? list.get(index) : null;
}
@Nullable
public static GoTypeSpec getTypeSpecSafe(@NotNull GoType type) {
GoTypeStub stub = type.getStub();
PsiElement parent = stub == null ? type.getParent() : stub.getParentStub().getPsi();
return ObjectUtils.tryCast(parent, GoTypeSpec.class);
}
public static boolean canBeAutoImported(@NotNull GoFile file, boolean allowMain, @Nullable Module module) {
if (isBuiltinFile(file) || !allowMain && StringUtil.equals(file.getPackageName(), GoConstants.MAIN)) {
return false;
}
return allowed(file, null, module) && !GoUtil.isExcludedFile(file);
}
@Nullable
@Contract("null, _ -> null")
public static <T extends PsiElement> T getNonStrictTopmostParentOfType(@Nullable PsiElement element, @NotNull Class<T> aClass) {
T first = PsiTreeUtil.getNonStrictParentOfType(element, aClass);
T topMost = PsiTreeUtil.getTopmostParentOfType(first, aClass);
return ObjectUtils.chooseNotNull(topMost, first);
}
@Nullable
public static GoExpression getExpression(@NotNull GoIndexOrSliceExpr slice) {
return ContainerUtil.getFirstItem(getExpressionsBefore(slice.getExpressionList(), slice.getLbrack()));
}
@NotNull
public static List<GoExpression> getLeftExpressionsList(@NotNull GoRangeClause rangeClause) {
return getExpressionsBefore(rangeClause.getExpressionList(), rangeClause.getRange());
}
@NotNull
public static List<GoExpression> getLeftExpressionsList(@NotNull GoRecvStatement recvStatement) {
return getExpressionsBefore(recvStatement.getExpressionList(),
ObjectUtils.chooseNotNull(recvStatement.getAssign(), recvStatement.getVarAssign()));
}
@NotNull
public static Trinity<GoExpression, GoExpression, GoExpression> getIndices(@NotNull GoIndexOrSliceExpr slice) {
GoExpression start;
GoExpression end = null;
GoExpression max = null;
ASTNode[] colons = slice.getNode().getChildren(TokenSet.create(GoTypes.COLON));
List<GoExpression> exprList = slice.getExpressionList();
start = ContainerUtil.getFirstItem(getExpressionsInRange(exprList, slice.getLbrack(), colons.length > 0 ? colons[0].getPsi() : null));
if (colons.length == 1) {
end = ContainerUtil.getFirstItem(getExpressionsInRange(exprList, colons[0].getPsi(), slice.getRbrack()));
}
if (colons.length == 2) {
end = ContainerUtil.getFirstItem(getExpressionsInRange(exprList, colons[0].getPsi(), colons[1].getPsi()));
max = ContainerUtil.getFirstItem(getExpressionsInRange(exprList, colons[1].getPsi(), slice.getRbrack()));
}
return Trinity.create(start, end, max);
}
@NotNull
public static List<GoExpression> getRightExpressionsList(@NotNull GoVarSpec varSpec) {
return varSpec.getExpressionList();
}
@NotNull
public static List<GoExpression> getRightExpressionsList(@NotNull GoRangeClause rangeClause) {
return ContainerUtil.createMaybeSingletonList(rangeClause.getRangeExpression());
}
@NotNull
public static List<GoExpression> getRightExpressionsList(@NotNull GoRecvStatement recvStatement) {
return ContainerUtil.createMaybeSingletonList(recvStatement.getRecvExpression());
}
@Nullable
public static GoExpression getRangeExpression(@NotNull GoRangeClause rangeClause) {
return getLastExpressionAfter(rangeClause.getExpressionList(), rangeClause.getRange());
}
@Nullable
public static GoExpression getRecvExpression(@NotNull GoRecvStatement recvStatement) {
return getLastExpressionAfter(recvStatement.getExpressionList(),
ObjectUtils.chooseNotNull(recvStatement.getAssign(), recvStatement.getVarAssign()));
}
@Nullable
public static GoExpression getSendExpression(@NotNull GoSendStatement sendStatement) {
return getLastExpressionAfter(sendStatement.getExpressionList(), sendStatement.getSendChannel());
}
@Nullable
private static GoExpression getLastExpressionAfter(@NotNull List<GoExpression> list, @Nullable PsiElement anchor) {
if (anchor == null) return null;
GoExpression last = ContainerUtil.getLastItem(list);
return last != null && last.getTextRange().getStartOffset() >= anchor.getTextRange().getEndOffset() ? last : null;
}
@NotNull
private static List<GoExpression> getExpressionsInRange(@NotNull List<GoExpression> list,
@Nullable PsiElement start,
@Nullable PsiElement end) {
if (start == null && end == null) {
return list;
}
return ContainerUtil.filter(list, expression -> (end == null || expression.getTextRange().getEndOffset() <= end.getTextRange().getStartOffset()) &&
(start == null || expression.getTextRange().getStartOffset() >= start.getTextRange().getEndOffset()));
}
@NotNull
private static List<GoExpression> getExpressionsBefore(@NotNull List<GoExpression> list, @Nullable PsiElement anchor) {
return getExpressionsInRange(list, null, anchor);
}
@NotNull
public static ReadWriteAccessDetector.Access getReadWriteAccess(@NotNull GoReferenceExpression referenceExpression) {
GoExpression expression = getConsiderableExpression(referenceExpression);
PsiElement parent = expression.getParent();
if (parent instanceof GoSelectorExpr) {
if (expression.equals(((GoSelectorExpr)parent).getRight())) {
expression = getConsiderableExpression((GoSelectorExpr)parent);
parent = expression.getParent();
}
else {
return Read;
}
}
if (parent instanceof GoIncDecStatement) {
return Write;
}
if (parent instanceof GoLeftHandExprList) {
PsiElement grandParent = parent.getParent();
if (grandParent instanceof GoAssignmentStatement) {
return ((GoAssignmentStatement)grandParent).getAssignOp().getAssign() == null ? ReadWrite : Write;
}
if (grandParent instanceof GoSendStatement) {
return Write;
}
return Read;
}
if (parent instanceof GoSendStatement && parent.getParent() instanceof GoCommCase) {
return expression.equals(((GoSendStatement)parent).getSendExpression()) ? Read : ReadWrite;
}
if (parent instanceof GoRangeClause) {
return expression.equals(((GoRangeClause)parent).getRangeExpression()) ? Read : Write;
}
if (parent instanceof GoRecvStatement) {
return expression.equals(((GoRecvStatement)parent).getRecvExpression()) ? Read : Write;
}
return Read;
}
@NotNull
private static GoExpression getConsiderableExpression(@NotNull GoExpression element) {
GoExpression result = element;
while (true) {
PsiElement parent = result.getParent();
if (parent == null) {
return result;
}
if (parent instanceof GoParenthesesExpr) {
result = (GoParenthesesExpr)parent;
continue;
}
if (parent instanceof GoUnaryExpr) {
GoUnaryExpr unaryExpr = (GoUnaryExpr)parent;
if (unaryExpr.getMul() != null || unaryExpr.getBitAnd() != null || unaryExpr.getSendChannel() != null) {
result = (GoUnaryExpr)parent;
continue;
}
}
return result;
}
}
@NotNull
public static String getDecodedText(@NotNull GoStringLiteral o) {
StringBuilder builder = new StringBuilder();
TextRange range = ElementManipulators.getManipulator(o).getRangeInElement(o);
o.createLiteralTextEscaper().decode(range, builder);
return builder.toString();
}
@Nullable
public static PsiElement getOperator(@NotNull GoUnaryExpr o) {
return getNotNullElement(o.getNot(), o.getMinus(), o.getPlus(), o.getBitAnd(), o.getBitXor(), o.getMul(), o.getSendChannel());
}
@Nullable
public static PsiElement getOperator(@NotNull GoBinaryExpr o) {
if (o instanceof GoAndExpr) return ((GoAndExpr)o).getCondAnd();
if (o instanceof GoOrExpr) return ((GoOrExpr)o).getCondOr();
if (o instanceof GoSelectorExpr) return ((GoSelectorExpr)o).getDot();
if (o instanceof GoConversionExpr) return ((GoConversionExpr)o).getComma();
if (o instanceof GoMulExpr) {
GoMulExpr m = (GoMulExpr)o;
return getNotNullElement(m.getMul(), m.getQuotient(), m.getRemainder(), m.getShiftRight(), m.getShiftLeft(), m.getBitAnd(),
m.getBitClear());
}
if (o instanceof GoAddExpr) {
GoAddExpr a = (GoAddExpr)o;
return getNotNullElement(a.getBitXor(), a.getBitOr(), a.getMinus(), a.getPlus());
}
if (o instanceof GoConditionalExpr) {
GoConditionalExpr c = (GoConditionalExpr)o;
return getNotNullElement(c.getEq(), c.getNotEq(), c.getGreater(), c.getGreaterOrEqual(), c.getLess(), c.getLessOrEqual());
}
return null;
}
@Nullable
private static PsiElement getNotNullElement(@Nullable PsiElement... elements) {
if (elements == null) return null;
for (PsiElement e : elements) {
if (e != null) return e;
}
return null;
}
public static boolean isSingleCharLiteral(@NotNull GoStringLiteral literal) {
return literal.getDecodedText().length() == 1;
}
@Nullable
public static GoExpression getRightExpression(@NotNull GoAssignmentStatement assignment, @NotNull GoExpression leftExpression) {
int fieldIndex = assignment.getLeftHandExprList().getExpressionList().indexOf(leftExpression);
return getByIndex(assignment.getExpressionList(), fieldIndex);
}
}