Support expected type from explicit cast

This commit support the following case.
Suppose we have such declaration:
  fun <T> foo(): T { ... }

Then in code we want to use it like this: `foo() as String`.
But in LV <= 1.1 we have type inference error: "Not enough
information for type parameter `T`". This error happened because we
do not use type from cast as expected type for call.

In this commit we fix this problem and use this type as expected type
in following cases:
 - our function has only one type parameter (this can be relaxed later)
 - function parameter types and extension receiver type not contains `T`

Also this fix problem with `findViewById`.
Already signature was: `fun findViewById(...): View`
and was used like: `findViewById() as MyView`.
New signature is `fun <T : View> findViewById(...): T`
and old usage was broken because of problem described above
This commit is contained in:
Stanislav Erokhin
2017-09-21 16:05:41 +03:00
committed by Ilya Gorbunov
parent ac508a510e
commit 4932fa1ddd
21 changed files with 412 additions and 1 deletions
@@ -265,6 +265,7 @@ public interface BindingContext {
WritableSlice<FqNameUnsafe, ClassDescriptor> FQNAME_TO_CLASS_DESCRIPTOR = new BasicWritableSlice<>(DO_NOTHING, true);
WritableSlice<KtFile, PackageFragmentDescriptor> FILE_TO_PACKAGE_FRAGMENT = Slices.createSimpleSlice();
WritableSlice<FqName, Collection<KtFile>> PACKAGE_TO_FILES = Slices.createSimpleSlice();
WritableSlice<KtBinaryExpressionWithTypeRHS, Boolean> CAST_TYPE_USED_AS_EXPECTED_TYPE = Slices.createSimpleSlice();
WritableSlice<KtFunction, KotlinResolutionCallbacksImpl.LambdaInfo> NEW_INFERENCE_LAMBDA_INFO = new BasicWritableSlice<>(DO_NOTHING);
@@ -20,11 +20,16 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.ReflectionTypes
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype
import org.jetbrains.kotlin.builtins.isFunctionTypeOrSubtype
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil
import org.jetbrains.kotlin.resolve.TemporaryBindingTrace
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.*
@@ -39,6 +44,7 @@ import org.jetbrains.kotlin.resolve.calls.context.ResolutionResultsCache
import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache
import org.jetbrains.kotlin.resolve.calls.inference.*
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPosition
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.RECEIVER_POSITION
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.VALUE_PARAMETER_POSITION
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ValidityConstraintForConstituentType
@@ -53,12 +59,16 @@ import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.TypeUtils.DONT_CARE
import org.jetbrains.kotlin.types.expressions.ControlStructureTypingUtils.ResolveConstruct
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
import org.jetbrains.kotlin.types.typeUtil.contains
import org.jetbrains.kotlin.types.typeUtil.makeNullable
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
private val SPECIAL_FUNCTION_NAMES = ResolveConstruct.values().map { it.specialFunctionName }.toSet()
class GenericCandidateResolver(
private val argumentTypeResolver: ArgumentTypeResolver,
private val coroutineInferenceSupport: CoroutineInferenceSupport
private val coroutineInferenceSupport: CoroutineInferenceSupport,
private val languageVersionSettings: LanguageVersionSettings
) {
fun <D : CallableDescriptor> inferTypeArguments(context: CallCandidateResolutionContext<D>): ResolutionStatus {
val candidateCall = context.candidateCall
@@ -116,11 +126,68 @@ class GenericCandidateResolver(
// Solution
val hasContradiction = constraintSystem.status.hasContradiction()
if (!hasContradiction) {
addExpectedTypeForExplicitCast(context, builder)
return INCOMPLETE_TYPE_INFERENCE
}
return OTHER_ERROR
}
private fun ConstraintSystem.Builder.typeInSystem(call: Call, type: KotlinType?): KotlinType? =
type?.let {
typeVariableSubstitutors[call.toHandle()]?.substitute(it, Variance.INVARIANT)
}
private fun FunctionDescriptor.isFunctionForExpectTypeFromCastFeature(): Boolean {
val typeParameter = typeParameters.singleOrNull() ?: return false
val returnType = returnType ?: return false
if (returnType is DeferredType && returnType.isComputing) return false
if (returnType.constructor != typeParameter.typeConstructor) return false
fun KotlinType.isBadType() = contains { it.constructor == typeParameter.typeConstructor }
if (valueParameters.any { it.type.isBadType() } || extensionReceiverParameter?.type?.isBadType() == true) return false
return true
}
private fun addExpectedTypeForExplicitCast(
context: CallCandidateResolutionContext<*>,
builder: ConstraintSystem.Builder
) {
if (!languageVersionSettings.supportsFeature(LanguageFeature.ExpectedTypeFromCast)) return
if (context.candidateCall is VariableAsFunctionResolvedCall) return
val candidateDescriptor = context.candidateCall.candidateDescriptor.safeAs<FunctionDescriptor>() ?: return
val binaryParent = getBinaryWithTypeParent(context.call.calleeExpression) ?: return
val operationType = binaryParent.operationReference.getReferencedNameElementType().takeIf {
it == KtTokens.AS_KEYWORD || it == KtTokens.AS_SAFE
} ?: return
val leftType = context.trace.get(BindingContext.TYPE, binaryParent.right ?: return) ?: return
val expectedType = if (operationType == KtTokens.AS_SAFE) leftType.makeNullable() else leftType
if (context.candidateCall.call.typeArgumentList != null || !candidateDescriptor.isFunctionForExpectTypeFromCastFeature()) return
val typeInSystem = builder.typeInSystem(context.call, candidateDescriptor.returnType ?: return) ?: return
context.trace.record(BindingContext.CAST_TYPE_USED_AS_EXPECTED_TYPE, binaryParent)
builder.addSubtypeConstraint(typeInSystem, expectedType, ConstraintPositionKind.SPECIAL.position())
}
private fun getBinaryWithTypeParent(calleeExpression: KtExpression?): KtBinaryExpressionWithTypeRHS? {
val callExpression = calleeExpression?.parent.safeAs<KtCallExpression>() ?: return null
val parent = callExpression.parent
return when (parent) {
is KtBinaryExpressionWithTypeRHS -> parent
is KtQualifiedExpression -> parent.parent.safeAs<KtBinaryExpressionWithTypeRHS>().takeIf { parent.selectorExpression == callExpression }
else -> null
}
}
private fun addValidityConstraintsForConstituentTypes(builder: ConstraintSystem.Builder, type: KotlinType) {
val typeConstructor = type.constructor
if (typeConstructor.declarationDescriptor is TypeParameterDescriptor) return
@@ -366,6 +366,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
) {
if (actualType == null || noExpectedType(targetType) || KotlinTypeKt.isError(targetType)) return;
if (Boolean.TRUE.equals(context.trace.get(BindingContext.CAST_TYPE_USED_AS_EXPECTED_TYPE, expression))) return;
if (DynamicTypesKt.isDynamic(targetType)) {
KtTypeReference right = expression.getRight();
assert right != null : "We know target is dynamic, but RHS is missing";
@@ -0,0 +1,19 @@
// IGNORE_BACKEND: JS, NATIVE
// LANGUAGE_VERSION: 1.2
// WITH_RUNTIME
import kotlin.test.assertEquals
inline fun <reified T> foo(): T {
return T::class.java.getName() as T
}
fun box(): String {
val fooCall = foo() as String
assertEquals("java.lang.String", fooCall)
val safeFooCall = foo() as? String
assertEquals("java.lang.String", safeFooCall)
return "OK"
}
@@ -0,0 +1,12 @@
// !LANGUAGE: +ExpectedTypeFromCast
fun foo() = 1
fun <T> foo() = foo() <!UNCHECKED_CAST!>as T<!>
fun <T> foo2(): T = TODO()
val test = <!TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>foo2<!>().<!DEBUG_INFO_ELEMENT_WITH_ERROR_TYPE!>plus<!>("") as String
fun <T> T.bar() = this
val barTest = "".bar() <!CAST_NEVER_SUCCEEDS!>as<!> Number
@@ -0,0 +1,8 @@
package
public val barTest: kotlin.Number
public val test: kotlin.String
public fun foo(): kotlin.Int
public fun </*0*/ T> foo(): T
public fun </*0*/ T> foo2(): T
public fun </*0*/ T> T.bar(): T
@@ -0,0 +1,20 @@
// !LANGUAGE: +ExpectedTypeFromCast
fun <T> foo(): T = TODO()
fun <V> id(value: V) = value
val asString = foo() as String
val viaId = <!TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>id<!>(<!TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>foo<!>()) as String
val insideId = id(foo() as String)
val asList = foo() as List<String>
val asStarList = foo() as List<*>
val safeAs = foo() as? String
val fromIs = <!TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>foo<!>() is String
val fromNoIs = <!TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>foo<!>() !is String
@@ -0,0 +1,12 @@
package
public val asList: kotlin.collections.List<kotlin.String>
public val asStarList: kotlin.collections.List<*>
public val asString: kotlin.String
public val fromIs: kotlin.Boolean
public val fromNoIs: kotlin.Boolean
public val insideId: kotlin.String
public val safeAs: kotlin.String?
public val viaId: kotlin.String
public fun </*0*/ T> foo(): T
public fun </*0*/ V> id(/*0*/ value: V): V
@@ -0,0 +1,16 @@
// !LANGUAGE: +ExpectedTypeFromCast
package pp
class A {
fun <T> foo(): T = TODO()
companion object {
fun <T> foo2(): T = TODO()
}
}
val x = A().foo() as String
val y = A.foo2() as String
val z = pp.A.foo2() as String
@@ -0,0 +1,23 @@
package
package pp {
public val x: kotlin.String
public val y: kotlin.String
public val z: kotlin.String
public final class A {
public constructor A()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun </*0*/ T> foo(): T
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public companion object Companion {
private constructor Companion()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun </*0*/ T> foo2(): T
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
}
@@ -0,0 +1,17 @@
// !LANGUAGE: +ExpectedTypeFromCast
class X<S> {
fun <T : S> foo(): T = TODO()
}
fun test(x: X<Number>) {
val <!UNUSED_VARIABLE!>y<!> = x.foo() as Int
}
fun <S, D: S> g() {
fun <T : S> foo(): T = TODO()
val <!UNUSED_VARIABLE!>y<!> = <!TYPE_INFERENCE_UPPER_BOUND_VIOLATED!>foo<!>() as Int
val <!UNUSED_VARIABLE!>y2<!> = foo() as D
}
@@ -0,0 +1,12 @@
package
public fun </*0*/ S, /*1*/ D : S> g(): kotlin.Unit
public fun test(/*0*/ x: X<kotlin.Number>): kotlin.Unit
public final class X</*0*/ S> {
public constructor X</*0*/ S>()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun </*0*/ T : S> foo(): T
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,58 @@
// !LANGUAGE: +ExpectedTypeFromCast
// !DIAGNOSTICS: -UNUSED_VARIABLE -DEBUG_INFO_LEAKING_THIS
// FILE: a/View.java
package a;
public class View {
}
// FILE: a/Test.java
package a;
public class Test {
public <T extends View> T findViewById(int id);
}
// FILE: 1.kt
package a
class X : View()
class Y<T> : View()
val xExplicit: X = Test().findViewById(0)
val xCast = Test().findViewById(0) as X
val xCastExplicitType = Test().findViewById<X>(0) as X
val xSafeCastExplicitType = Test().findViewById<X>(0) <!USELESS_CAST!>as? X<!>
val yExplicit: Y<String> = Test().findViewById(0)
val yCast = Test().findViewById(0) as Y<String>
class TestChild : Test() {
val xExplicit: X = findViewById(0)
val xCast = findViewById(0) as X
val yExplicit: Y<String> = findViewById(0)
val yCast = findViewById(0) as Y<String>
}
fun test(t: Test) {
val xExplicit: X = t.findViewById(0)
val xCast = t.findViewById(0) as X
val yExplicit: Y<String> = t.findViewById(0)
val yCast = t.findViewById(0) as Y<String>
}
fun test2(t: Test?) {
val xSafeCallSafeCast = t?.findViewById(0) as? X
val xSafeCallSafeCastExplicitType = t?.findViewById<X>(0) <!USELESS_CAST!>as? X<!>
val xSafeCallCast = t?.findViewById(0) as X
val xSafeCallCastExplicitType = t<!UNNECESSARY_SAFE_CALL!>?.<!>findViewById<X>(0) as X
}
@@ -0,0 +1,53 @@
package
package a {
public val xCast: a.X
public val xCastExplicitType: a.X
public val xExplicit: a.X
public val xSafeCastExplicitType: a.X?
public val yCast: a.Y<kotlin.String>
public val yExplicit: a.Y<kotlin.String>
public fun test(/*0*/ t: a.Test): kotlin.Unit
public fun test2(/*0*/ t: a.Test?): kotlin.Unit
public open class Test {
public constructor Test()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open fun </*0*/ T : a.View!> findViewById(/*0*/ id: kotlin.Int): T!
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class TestChild : a.Test {
public constructor TestChild()
public final val xCast: a.X
public final val xExplicit: a.X
public final val yCast: a.Y<kotlin.String>
public final val yExplicit: a.Y<kotlin.String>
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun </*0*/ T : a.View!> findViewById(/*0*/ id: kotlin.Int): T!
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public open class View {
public constructor View()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class X : a.View {
public constructor X()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class Y</*0*/ T> : a.View {
public constructor Y</*0*/ T>()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
@@ -17825,6 +17825,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName);
}
@TestMetadata("expectedTypeFromCast.kt")
public void testExpectedTypeFromCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reified/expectedTypeFromCast.kt");
doTest(fileName);
}
@TestMetadata("filterIsInstance.kt")
public void testFilterIsInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reified/filterIsInstance.kt");
@@ -10222,6 +10222,36 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("expectedTypeAdditionalTest.kt")
public void testExpectedTypeAdditionalTest() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/expectedTypeAdditionalTest.kt");
doTest(fileName);
}
@TestMetadata("expectedTypeFromCast.kt")
public void testExpectedTypeFromCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/expectedTypeFromCast.kt");
doTest(fileName);
}
@TestMetadata("expectedTypeFromCastComplexExpression.kt")
public void testExpectedTypeFromCastComplexExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/expectedTypeFromCastComplexExpression.kt");
doTest(fileName);
}
@TestMetadata("expectedTypeWithGenerics.kt")
public void testExpectedTypeWithGenerics() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/expectedTypeWithGenerics.kt");
doTest(fileName);
}
@TestMetadata("findViewById.kt")
public void testFindViewById() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/findViewById.kt");
doTest(fileName);
}
@TestMetadata("fixVariableToNothing.kt")
public void testFixVariableToNothing() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/fixVariableToNothing.kt");
@@ -10222,6 +10222,36 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
doTest(fileName);
}
@TestMetadata("expectedTypeAdditionalTest.kt")
public void testExpectedTypeAdditionalTest() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/expectedTypeAdditionalTest.kt");
doTest(fileName);
}
@TestMetadata("expectedTypeFromCast.kt")
public void testExpectedTypeFromCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/expectedTypeFromCast.kt");
doTest(fileName);
}
@TestMetadata("expectedTypeFromCastComplexExpression.kt")
public void testExpectedTypeFromCastComplexExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/expectedTypeFromCastComplexExpression.kt");
doTest(fileName);
}
@TestMetadata("expectedTypeWithGenerics.kt")
public void testExpectedTypeWithGenerics() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/expectedTypeWithGenerics.kt");
doTest(fileName);
}
@TestMetadata("findViewById.kt")
public void testFindViewById() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/findViewById.kt");
doTest(fileName);
}
@TestMetadata("fixVariableToNothing.kt")
public void testFixVariableToNothing() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/fixVariableToNothing.kt");
@@ -17825,6 +17825,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("expectedTypeFromCast.kt")
public void testExpectedTypeFromCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reified/expectedTypeFromCast.kt");
doTest(fileName);
}
@TestMetadata("filterIsInstance.kt")
public void testFilterIsInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reified/filterIsInstance.kt");
@@ -17825,6 +17825,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName);
}
@TestMetadata("expectedTypeFromCast.kt")
public void testExpectedTypeFromCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reified/expectedTypeFromCast.kt");
doTest(fileName);
}
@TestMetadata("filterIsInstance.kt")
public void testFilterIsInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reified/filterIsInstance.kt");
@@ -64,6 +64,7 @@ enum class LanguageFeature(
ThrowNpeOnExplicitEqualsForBoxedNull(KOTLIN_1_2),
JvmPackageName(KOTLIN_1_2),
AssigningArraysToVarargsInNamedFormInAnnotations(KOTLIN_1_2),
ExpectedTypeFromCast(KOTLIN_1_2),
ReturnsEffect(KOTLIN_1_3),
CallsInPlaceEffect(KOTLIN_1_3),
@@ -21401,6 +21401,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
}
@TestMetadata("expectedTypeFromCast.kt")
public void testExpectedTypeFromCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reified/expectedTypeFromCast.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
}
@TestMetadata("filterIsInstance.kt")
public void testFilterIsInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reified/filterIsInstance.kt");