Quick-Fixes: Approximate non-denotable type with nearest denotable supertype
#KT-5915 Fixed
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+45
-18
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user