/*
* Copyright 2010-2015 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.idea.core.quickfix;
import com.intellij.extapi.psi.ASTDelegatePsiElement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import org.jetbrains.kotlin.types.DeferredType;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
public class QuickFixUtil {
private QuickFixUtil() {
}
public static boolean removePossiblyWhiteSpace(ASTDelegatePsiElement element, PsiElement possiblyWhiteSpace) {
if (possiblyWhiteSpace instanceof PsiWhiteSpace) {
element.deleteChildInternal(possiblyWhiteSpace.getNode());
return true;
}
return false;
}
@Nullable
public static <T extends PsiElement> T getParentElementOfType(Diagnostic diagnostic, Class<T> aClass) {
return PsiTreeUtil.getParentOfType(diagnostic.getPsiElement(), aClass, false);
}
@Nullable
public static KotlinType getDeclarationReturnType(KtNamedDeclaration declaration) {
PsiFile file = declaration.getContainingFile();
if (!(file instanceof KtFile)) return null;
DeclarationDescriptor descriptor = ResolutionUtils.resolveToDescriptor(declaration, BodyResolveMode.FULL);
if (!(descriptor instanceof CallableDescriptor)) return null;
KotlinType type = ((CallableDescriptor) descriptor).getReturnType();
if (type instanceof DeferredType) {
type = ((DeferredType) type).getDelegate();
}
return type;
}
@Nullable
public static KotlinType findLowerBoundOfOverriddenCallablesReturnTypes(@NotNull CallableDescriptor descriptor) {
KotlinType matchingReturnType = null;
for (CallableDescriptor overriddenDescriptor : ((CallableDescriptor) descriptor).getOverriddenDescriptors()) {
KotlinType overriddenReturnType = overriddenDescriptor.getReturnType();
if (overriddenReturnType == null) {
return null;
}
if (matchingReturnType == null || KotlinTypeChecker.DEFAULT.isSubtypeOf(overriddenReturnType, matchingReturnType)) {
matchingReturnType = overriddenReturnType;
}
else if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(matchingReturnType, overriddenReturnType)) {
return null;
}
}
return matchingReturnType;
}
@Nullable
public static PsiElement safeGetDeclaration(@Nullable CallableDescriptor descriptor) {
//do not create fix if descriptor has more than one overridden declaration
if (descriptor == null || descriptor.getOverriddenDescriptors().size() > 1) return null;
return DescriptorToSourceUtils.descriptorToDeclaration(descriptor);
}
@Nullable
public static KtParameter getParameterDeclarationForValueArgument(
@NotNull ResolvedCall<?> resolvedCall,
@Nullable ValueArgument valueArgument
) {
PsiElement declaration = safeGetDeclaration(CallUtilKt.getParameterForArgument(resolvedCall, valueArgument));
return declaration instanceof KtParameter ? (KtParameter) declaration : null;
}
private static boolean equalOrLastInThenOrElse(KtExpression thenOrElse, KtExpression expression) {
if (thenOrElse == expression) return true;
return thenOrElse instanceof KtBlockExpression && expression.getParent() == thenOrElse &&
PsiTreeUtil.getNextSiblingOfType(expression, KtExpression.class) == null;
}
@Nullable
public static KtIfExpression getParentIfForBranch(@Nullable KtExpression expression) {
KtIfExpression ifExpression = PsiTreeUtil.getParentOfType(expression, KtIfExpression.class, true);
if (ifExpression == null) return null;
if (equalOrLastInThenOrElse(ifExpression.getThen(), expression)
|| equalOrLastInThenOrElse(ifExpression.getElse(), expression)) {
return ifExpression;
}
return null;
}
// Returns true iff parent's value always or sometimes is evaluable to child's value, e.g.
// parent = (x), child = x;
// parent = if (...) x else y, child = x;
// parent = y.x, child = x
public static boolean canEvaluateTo(KtExpression parent, KtExpression child) {
if (parent == null || child == null) {
return false;
}
while (parent != child) {
PsiElement childParent = child.getParent();
if (childParent instanceof KtParenthesizedExpression) {
child = (KtExpression) childParent;
continue;
}
if (childParent instanceof KtDotQualifiedExpression &&
(child instanceof KtCallExpression || child instanceof KtDotQualifiedExpression)) {
child = (KtExpression) childParent;
continue;
}
child = getParentIfForBranch(child);
if (child == null) return false;
}
return true;
}
public static boolean canFunctionOrGetterReturnExpression(@NotNull KtDeclaration functionOrGetter, @NotNull KtExpression expression) {
if (functionOrGetter instanceof KtFunctionLiteral) {
KtBlockExpression functionLiteralBody = ((KtFunctionLiteral) functionOrGetter).getBodyExpression();
PsiElement returnedElement = functionLiteralBody == null ? null : functionLiteralBody.getLastChild();
return returnedElement instanceof KtExpression && canEvaluateTo((KtExpression) returnedElement, expression);
}
else {
if (functionOrGetter instanceof KtDeclarationWithInitializer && canEvaluateTo(((KtDeclarationWithInitializer) functionOrGetter).getInitializer(), expression)) {
return true;
}
KtReturnExpression returnExpression = PsiTreeUtil.getParentOfType(expression, KtReturnExpression.class);
return returnExpression != null && canEvaluateTo(returnExpression.getReturnedExpression(), expression);
}
}
public static String renderTypeWithFqNameOnClash(KotlinType type, String nameToCheckAgainst) {
FqName fqNameToCheckAgainst = new FqName(nameToCheckAgainst);
ClassifierDescriptor typeClassifierDescriptor = type.getConstructor().getDeclarationDescriptor();
FqName typeFqName = typeClassifierDescriptor != null ? DescriptorUtils.getFqNameSafe(typeClassifierDescriptor) : fqNameToCheckAgainst;
DescriptorRenderer renderer = typeFqName.shortName().equals(fqNameToCheckAgainst.shortName())
? IdeDescriptorRenderers.SOURCE_CODE
: IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES;
return renderer.renderType(type);
}
}