Quick-Fixes: Approximate non-denotable type with nearest denotable supertype

#KT-5915 Fixed
This commit is contained in:
Alexey Sedunov
2015-03-16 15:33:08 +03:00
parent a2162810ff
commit 1ed2c82008
20 changed files with 251 additions and 46 deletions
@@ -19,12 +19,17 @@ package org.jetbrains.kotlin.resolve.scopes;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.kotlin.descriptors.PropertyDescriptor;
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor;
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.psi.JetClassBody;
import org.jetbrains.kotlin.psi.JetClassOrObject;
import org.jetbrains.kotlin.psi.JetExpression;
import org.jetbrains.kotlin.psi.JetFile;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.TraceBasedRedeclarationHandler;
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
@@ -151,4 +156,25 @@ public final class JetScopeUtils {
}
return out.toString();
}
@Nullable
public static JetScope getResolutionScope(@NotNull JetExpression expression, @NotNull AnalysisResult analysisResult) {
PsiElement parent = expression.getParent();
if (parent instanceof JetClassBody) {
JetClassOrObject classOrObject = (JetClassOrObject) parent.getParent();
ClassDescriptor classDescriptor = analysisResult.getBindingContext().get(BindingContext.CLASS, classOrObject);
if (classDescriptor instanceof ClassDescriptorWithResolutionScopes) {
return ((ClassDescriptorWithResolutionScopes) classDescriptor).getScopeForMemberDeclarationResolution();
}
return null;
}
if (parent instanceof JetFile) {
PackageViewDescriptor packageView = analysisResult.getModuleDescriptor().getPackage(((JetFile) parent).getPackageFqName());
return packageView != null ? packageView.getMemberScope() : null;
}
return analysisResult.getBindingContext().get(BindingContext.RESOLUTION_SCOPE, expression);
}
}
@@ -20,22 +20,31 @@ import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import kotlin.Function1;
import kotlin.KotlinPackage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters1;
import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters2;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages;
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
import org.jetbrains.kotlin.idea.imports.ImportsPackage;
import org.jetbrains.kotlin.idea.util.UtilPackage;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilPackage;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.scopes.JetScope;
import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils;
import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.TypeUtils;
import java.util.Collections;
import java.util.LinkedList;
@@ -45,12 +54,36 @@ import java.util.List;
public class QuickFixFactoryForTypeMismatchError extends JetIntentionActionsFactory {
private final static Logger LOG = Logger.getInstance(QuickFixFactoryForTypeMismatchError.class);
private static boolean isResolvableType(@NotNull JetType type, @Nullable JetScope scope) {
if (ImportsPackage.canBeReferencedViaImport(type)) return true;
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
if (descriptor == null || descriptor.getName().isSpecial()) return false;
return scope != null && scope.getClassifier(descriptor.getName()) == descriptor;
}
private static JetType approximateWithResolvableType(@NotNull JetType type, @Nullable final JetScope scope) {
if (isResolvableType(type, scope)) return type;
JetType superType = KotlinPackage.firstOrNull(
TypeUtils.getAllSupertypes(type),
new Function1<JetType, Boolean>() {
@Override
public Boolean invoke(JetType type) {
return isResolvableType(type, scope);
}
}
);
return superType != null ? superType : KotlinBuiltIns.getInstance().getAnyType();
}
@NotNull
@Override
protected List<IntentionAction> doCreateActions(@NotNull Diagnostic diagnostic) {
List<IntentionAction> actions = new LinkedList<IntentionAction>();
BindingContext context = ResolvePackage.analyzeFully((JetFile) diagnostic.getPsiFile());
AnalysisResult analysisResult = ResolvePackage.analyzeFullyAndGetResult((JetFile) diagnostic.getPsiFile());
BindingContext context = analysisResult.getBindingContext();
PsiElement diagnosticElement = diagnostic.getPsiElement();
if (!(diagnosticElement instanceof JetExpression)) {
@@ -97,9 +130,12 @@ public class QuickFixFactoryForTypeMismatchError extends JetIntentionActionsFact
JetProperty property = PsiTreeUtil.getParentOfType(expression, JetProperty.class);
if (property != null) {
JetPropertyAccessor getter = property.getGetter();
if (QuickFixUtil.canEvaluateTo(property.getInitializer(), expression) ||
JetExpression initializer = property.getInitializer();
if (QuickFixUtil.canEvaluateTo(initializer, expression) ||
(getter != null && QuickFixUtil.canFunctionOrGetterReturnExpression(property.getGetter(), expression))) {
actions.add(new ChangeVariableTypeFix(property, expressionType));
JetScope scope = JetScopeUtils.getResolutionScope(property, analysisResult);
JetType typeToInsert = approximateWithResolvableType(expressionType, scope);
actions.add(new ChangeVariableTypeFix(property, typeToInsert));
}
}
@@ -111,7 +147,9 @@ public class QuickFixFactoryForTypeMismatchError extends JetIntentionActionsFact
? BindingContextUtilPackage.getTargetFunction((JetReturnExpression) expressionParent, context)
: PsiTreeUtil.getParentOfType(expression, JetFunction.class, true);
if (function instanceof JetFunction && QuickFixUtil.canFunctionOrGetterReturnExpression(function, expression)) {
actions.add(new ChangeFunctionReturnTypeFix((JetFunction) function, expressionType));
JetScope scope = JetScopeUtils.getResolutionScope(function, analysisResult);
JetType typeToInsert = approximateWithResolvableType(expressionType, scope);
actions.add(new ChangeFunctionReturnTypeFix((JetFunction) function, typeToInsert));
}
// Fixing overloaded operators:
@@ -124,19 +162,6 @@ public class QuickFixFactoryForTypeMismatchError extends JetIntentionActionsFact
}
}
}
if (expression.getParent() instanceof JetBinaryExpression) {
JetBinaryExpression parentBinary = (JetBinaryExpression) expression.getParent();
if (parentBinary.getRight() == expression) {
ResolvedCall<?> resolvedCall = CallUtilPackage.getResolvedCall(parentBinary, context);
if (resolvedCall != null) {
JetFunction declaration = getFunctionDeclaration(resolvedCall);
if (declaration != null) {
JetParameter binaryOperatorParameter = declaration.getValueParameters().get(0);
actions.add(new ChangeParameterTypeFix(binaryOperatorParameter, expressionType));
}
}
}
}
// Change function return type when TYPE_MISMATCH is reported on call expression:
if (expression instanceof JetCallExpression) {
@@ -162,7 +187,9 @@ public class QuickFixFactoryForTypeMismatchError extends JetIntentionActionsFact
? expressionType
: context.get(BindingContext.EXPRESSION_TYPE, valueArgument.getArgumentExpression());
if (correspondingParameter != null && valueArgumentType != null) {
actions.add(new ChangeParameterTypeFix(correspondingParameter, valueArgumentType));
JetScope scope = JetScopeUtils.getResolutionScope(valueArgument.getArgumentExpression(), analysisResult);
JetType typeToInsert = approximateWithResolvableType(valueArgumentType, scope);
actions.add(new ChangeParameterTypeFix(correspondingParameter, typeToInsert));
}
}
}
@@ -23,13 +23,13 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes;
import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor;
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.scopes.JetScope;
import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils;
import java.util.HashSet;
import java.util.Set;
@@ -76,9 +76,7 @@ public class JetNameValidatorImpl extends JetNameValidator {
private boolean checkElement(String name, PsiElement sibling, final Set<JetScope> visitedScopes) {
if (!(sibling instanceof JetElement)) return true;
AnalysisResult analysisResult = ResolvePackage.analyzeAndGetResult((JetElement) sibling);
final BindingContext bindingContext = analysisResult.getBindingContext();
final ModuleDescriptor module = analysisResult.getModuleDescriptor();
final AnalysisResult analysisResult = ResolvePackage.analyzeAndGetResult((JetElement) sibling);
final Name identifier = Name.identifier(name);
final Ref<Boolean> result = new Ref<Boolean>(true);
@@ -90,29 +88,9 @@ public class JetNameValidatorImpl extends JetNameValidator {
}
}
@Nullable
private JetScope getScope(@NotNull JetExpression expression) {
PsiElement parent = expression.getParent();
if (parent instanceof JetClassBody) {
JetClassOrObject classOrObject = (JetClassOrObject) parent.getParent();
ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
return classDescriptor instanceof ClassDescriptorWithResolutionScopes
? ((ClassDescriptorWithResolutionScopes) classDescriptor).getScopeForMemberDeclarationResolution()
: null;
}
if (parent instanceof JetFile) {
PackageViewDescriptor packageViewDescriptor = module.getPackage(((JetFile) parent).getPackageFqName());
return packageViewDescriptor != null ? packageViewDescriptor.getMemberScope() : null;
}
return bindingContext.get(BindingContext.RESOLUTION_SCOPE, expression);
}
@Override
public void visitExpression(@NotNull JetExpression expression) {
JetScope resolutionScope = getScope(expression);
JetScope resolutionScope = JetScopeUtils.getResolutionScope(expression, analysisResult);
if (resolutionScope != null) {
if (!visitedScopes.add(resolutionScope)) return;
@@ -0,0 +1,8 @@
// "Change 'bar' function return type to 'A'" "true"
fun foo() {
open class A
fun bar(): A {
return object: A() {}
}
}
@@ -0,0 +1,10 @@
// "Change parameter 't' type of function 'foo' to 'T'" "true"
trait T
fun Int.foo(t: T) {
}
fun foo() {
1.foo(object: T{})
}
@@ -0,0 +1,8 @@
// "Change parameter 't' type of function 'foo' to 'T'" "true"
trait T
fun Int.foo(t: T) = this
fun foo() {
1 foo object: T{}
}
@@ -0,0 +1,6 @@
// "Change 't' type to 'T'" "true"
trait T
fun foo() {
val t: T = object: T{}
}
@@ -0,0 +1,6 @@
// "Change 'foo' function return type to 'T'" "true"
trait T
fun foo(): T {
return object: T{}
}
@@ -0,0 +1,6 @@
// "Change 'foo' function return type to 'Any'" "true"
fun foo(): Any {
class A
return A()
}
@@ -0,0 +1,10 @@
// "Change 'foo' function return type to 'U'" "true"
trait T
trait U
fun foo(): U {
open class A: T
class B: A(), U
return <caret>B()
}
@@ -0,0 +1,9 @@
// "Change 'foo' function return type to 'T'" "true"
trait T
fun foo(): T {
open class A: T
class B: A()
return B()
}
@@ -0,0 +1,8 @@
// "Change 'bar' function return type to 'A'" "true"
fun foo() {
open class A
fun bar(): Int {
return <caret>object: A() {}
}
}
@@ -0,0 +1,10 @@
// "Change parameter 't' type of function 'foo' to 'T'" "true"
trait T
fun Int.foo(t: Int) {
}
fun foo() {
1.foo(<caret>object: T{})
}
@@ -0,0 +1,8 @@
// "Change parameter 't' type of function 'foo' to 'T'" "true"
trait T
fun Int.foo(t: Int) = this
fun foo() {
1 foo <caret>object: T{}
}
@@ -0,0 +1,6 @@
// "Change 't' type to 'T'" "true"
trait T
fun foo() {
val t: Int = <caret>object: T{}
}
@@ -0,0 +1,6 @@
// "Change 'foo' function return type to 'T'" "true"
trait T
fun foo() {
return <caret>object: T{}
}
@@ -0,0 +1,6 @@
// "Change 'foo' function return type to 'Any'" "true"
fun foo() {
class A
return <caret>A()
}
@@ -0,0 +1,10 @@
// "Change 'foo' function return type to 'U'" "true"
trait T
trait U
fun foo() {
open class A: T
class B: A(), U
return <caret>B()
}
@@ -0,0 +1,9 @@
// "Change 'foo' function return type to 'T'" "true"
trait T
fun foo() {
open class A: T
class B: A()
return <caret>B()
}
@@ -4588,10 +4588,46 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
})
@RunWith(JUnit3RunnerWithInners.class)
public static class TypeMismatch extends AbstractQuickFixTest {
@TestMetadata("beforeAccessibleLocalClassInReturn.kt")
public void testAccessibleLocalClassInReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeAccessibleLocalClassInReturn.kt");
doTest(fileName);
}
public void testAllFilesPresentInTypeMismatch() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch"), Pattern.compile("^before(\\w+)\\.kt$"), true);
}
@TestMetadata("beforeAnonymousObjectInCall.kt")
public void testAnonymousObjectInCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeAnonymousObjectInCall.kt");
doTest(fileName);
}
@TestMetadata("beforeAnonymousObjectInInfixCall.kt")
public void testAnonymousObjectInInfixCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeAnonymousObjectInInfixCall.kt");
doTest(fileName);
}
@TestMetadata("beforeAnonymousObjectInInitializer.kt")
public void testAnonymousObjectInInitializer() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeAnonymousObjectInInitializer.kt");
doTest(fileName);
}
@TestMetadata("beforeAnonymousObjectInReturn.kt")
public void testAnonymousObjectInReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeAnonymousObjectInReturn.kt");
doTest(fileName);
}
@TestMetadata("beforeAnyInReturn.kt")
public void testAnyInReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeAnyInReturn.kt");
doTest(fileName);
}
@TestMetadata("beforeChangeFunctionLiteralParameterTypeToFunctionType.kt")
public void testChangeFunctionLiteralParameterTypeToFunctionType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeChangeFunctionLiteralParameterTypeToFunctionType.kt");
@@ -4676,6 +4712,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("beforeLocalClassInReturn1.kt")
public void testLocalClassInReturn1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeLocalClassInReturn1.kt");
doTest(fileName);
}
@TestMetadata("beforeLocalClassInReturn2.kt")
public void testLocalClassInReturn2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeLocalClassInReturn2.kt");
doTest(fileName);
}
@TestMetadata("beforeMakeReturnTypeNullable.kt")
public void testMakeReturnTypeNullable() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/beforeMakeReturnTypeNullable.kt");