Improve diagnostic on overload resolution ambiguity

Report type mismatch on argument when a nullable argument is passed to non-null parameter.

 Note that this affects only functions with simple types without generics

 #KT-2007 Fixed
 #KT-9282 Fixed
This commit is contained in:
Mikhail Zarechenskiy
2017-06-20 17:18:53 +03:00
parent 16de991b07
commit 7a9e1b2b1d
11 changed files with 82 additions and 11 deletions
@@ -53,6 +53,7 @@ import org.jetbrains.kotlin.types.checker.ErrorTypesAreEqualToAnything
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.expressions.DoubleColonExpressionResolver
import org.jetbrains.kotlin.types.typeUtil.containsTypeProjectionsInTopLevelArguments
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import java.util.*
class CandidateResolver(
@@ -376,7 +377,7 @@ class CandidateResolver(
if (!ArgumentTypeResolver.isSubtypeOfForArgumentType(type, expectedType)) {
val smartCast = smartCastValueArgumentTypeIfPossible(expression, newContext.expectedType, type, newContext)
if (smartCast == null) {
resultStatus = OTHER_ERROR
resultStatus = tryNotNullableArgument(type, expectedType) ?: OTHER_ERROR
matchStatus = ArgumentMatchStatus.TYPE_MISMATCH
}
else {
@@ -417,6 +418,14 @@ class CandidateResolver(
}
}
private fun tryNotNullableArgument(argumentType: KotlinType, parameterType: KotlinType): ResolutionStatus? {
if (!argumentType.isMarkedNullable || parameterType.isMarkedNullable) return null
val notNullableArgumentType = argumentType.makeNotNullable()
val isApplicable = ArgumentTypeResolver.isSubtypeOfForArgumentType(notNullableArgumentType, parameterType)
return if (isApplicable) NULLABLE_ARGUMENT_TYPE_MISMATCH else null
}
private fun CallCandidateResolutionContext<*>.checkReceiverTypeError(): Unit = check {
val extensionReceiver = candidateDescriptor.extensionReceiverParameter
val dispatchReceiver = candidateDescriptor.dispatchReceiverParameter
@@ -26,6 +26,7 @@ public enum ResolutionStatus {
WRONG_NUMBER_OF_TYPE_ARGUMENTS_ERROR,
UNSTABLE_SMARTCAST_ERROR,
INVISIBLE_MEMBER_ERROR,
NULLABLE_ARGUMENT_TYPE_MISMATCH,
OTHER_ERROR,
ARGUMENTS_MAPPING_ERROR,
// '1.foo()' shouldn't be resolved to 'fun String.foo()'
@@ -44,6 +45,7 @@ public enum ResolutionStatus {
EnumSet.of(WRONG_NUMBER_OF_TYPE_ARGUMENTS_ERROR),
EnumSet.of(UNSTABLE_SMARTCAST_ERROR),
EnumSet.of(INVISIBLE_MEMBER_ERROR),
EnumSet.of(NULLABLE_ARGUMENT_TYPE_MISMATCH),
EnumSet.of(OTHER_ERROR),
EnumSet.of(ARGUMENTS_MAPPING_ERROR),
EnumSet.of(RECEIVER_TYPE_ERROR),
@@ -0,0 +1,17 @@
// FILE: J.java
import org.jetbrains.annotations.*;
public class J {
void foo(String x) {}
void foo(@NotNull Double x) {}
void foo(@Nullable Byte x) {}
}
// FILE: test.kt
fun test(j: J, nullStr: String?, nullByte: Byte?, nullDouble: Double?) {
j.foo(nullStr)
j.foo(<!TYPE_MISMATCH!>nullDouble<!>)
j.foo(nullByte)
}
@@ -0,0 +1,13 @@
package
public fun test(/*0*/ j: J, /*1*/ nullStr: kotlin.String?, /*2*/ nullByte: kotlin.Byte?, /*3*/ nullDouble: kotlin.Double?): kotlin.Unit
public open class J {
public constructor J()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public/*package*/ open fun foo(/*0*/ @org.jetbrains.annotations.Nullable x: kotlin.Byte?): kotlin.Unit
public/*package*/ open fun foo(/*0*/ @org.jetbrains.annotations.NotNull x: kotlin.Double): kotlin.Unit
public/*package*/ open fun foo(/*0*/ x: kotlin.String!): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,12 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun foo(x: String) {}
fun foo(x: Int) {}
fun foo(x: Int, y: String) {}
fun bar(nullX: Int?, nullY: String?, notNullY: String) {
foo(<!TYPE_MISMATCH!>nullX<!>)
foo(<!TYPE_MISMATCH!>nullX<!>, notNullY)
foo(<!TYPE_MISMATCH!>nullX<!>, <!TYPE_MISMATCH!>nullY<!>)
<!NONE_APPLICABLE!>foo<!>()
}
@@ -0,0 +1,6 @@
package
public fun bar(/*0*/ nullX: kotlin.Int?, /*1*/ nullY: kotlin.String?, /*2*/ notNullY: kotlin.String): kotlin.Unit
public fun foo(/*0*/ x: kotlin.Int): kotlin.Unit
public fun foo(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.String): kotlin.Unit
public fun foo(/*0*/ x: kotlin.String): kotlin.Unit
@@ -33,7 +33,7 @@ fun test() {
platformJ++
1 + platformNN
1 <!NONE_APPLICABLE!>+<!> platformN
1 + <!TYPE_MISMATCH!>platformN<!>
1 + platformJ
platformNN + 1
@@ -41,7 +41,7 @@ fun test() {
platformJ + 1
1 <!INFIX_MODIFIER_REQUIRED!>plus<!> platformNN
1 <!NONE_APPLICABLE!>plus<!> platformN
1 <!INFIX_MODIFIER_REQUIRED!>plus<!> <!TYPE_MISMATCH!>platformN<!>
1 <!INFIX_MODIFIER_REQUIRED!>plus<!> platformJ
platformNN <!INFIX_MODIFIER_REQUIRED!>plus<!> 1
@@ -14,7 +14,7 @@ fun bar(arg: Long?): Long {
return i<!UNSAFE_CALL!>--<!> <!DEBUG_INFO_ELEMENT_WITH_ERROR_TYPE!>+<!> i
}
if (i++ == 7L) {
return i++ <!NONE_APPLICABLE!>+<!> i
return i++ <!UNSAFE_OPERATOR_CALL!>+<!> <!TYPE_MISMATCH!>i<!>
}
return 0L
}
@@ -1,6 +1,6 @@
// FILE: KotlinFile.kt
fun foo(javaInterface: JavaInterface) {
javaInterface.doIt(<!NULL_FOR_NONNULL_TYPE!>null<!>) <!TYPE_MISMATCH!>{ }<!>
javaInterface.doIt(<!NULL_FOR_NONNULL_TYPE!>null<!>) { }
javaInterface.doIt("", <!NULL_FOR_NONNULL_TYPE!>null<!>)
}
@@ -11,9 +11,9 @@ fun valuesNullable(map: MutableMap<Int, String?>) {
// ORIGINAL: fun merge(K, V, (V, V) -> V?): V? defined in kotlin.collections.MutableMap
// SUBSTITUTED: fun merge(Int, String, (String, String) -> String?): String? defined in kotlin.collections.MutableMap
map.merge(1, null) { old, new -> old + new }
// OTHER_ERROR
// ORIGINAL: fun merge(K, V, BiFunction<in V, in V, out V?>): V? defined in kotlin.collections.MutableMap
// SUBSTITUTED: fun merge(Int, String, BiFunction<in String, in String, out String?>): String? defined in kotlin.collections.MutableMap
// NULLABLE_ARGUMENT_TYPE_MISMATCH
// ORIGINAL: fun merge(K, V, (V, V) -> V?): V? defined in kotlin.collections.MutableMap
// SUBSTITUTED: fun merge(Int, String, (String, String) -> String?): String? defined in kotlin.collections.MutableMap
}
fun <T> valuesT(map: MutableMap<Int, T>, newValue: T) {
@@ -32,9 +32,9 @@ fun <T : Any> valuesTNotNull(map: MutableMap<Int, T>, newValue: T) {
fun <T : Any> valuesTNullable(map: MutableMap<Int, T?>, newValue: T?) {
map.merge(1, newValue) { old, new -> new }
// OTHER_ERROR
// ORIGINAL: fun merge(K, V, BiFunction<in V, in V, out V?>): V? defined in kotlin.collections.MutableMap
// SUBSTITUTED: fun merge(Int, T, BiFunction<in T, in T, out T?>): T? defined in kotlin.collections.MutableMap
// NULLABLE_ARGUMENT_TYPE_MISMATCH
// ORIGINAL: fun merge(K, V, (V, V) -> V?): V? defined in kotlin.collections.MutableMap
// SUBSTITUTED: fun merge(Int, T, (T, T) -> T?): T? defined in kotlin.collections.MutableMap
map.merge(1, newValue!!) { old, new -> new }
// SUCCESS
// ORIGINAL: fun merge(K, V, (V, V) -> V?): V? defined in kotlin.collections.MutableMap
@@ -14011,6 +14011,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("nullableArgumentToNonNullParameterPlatform.kt")
public void testNullableArgumentToNonNullParameterPlatform() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/nullableTypes/nullableArgumentToNonNullParameterPlatform.kt");
doTest(fileName);
}
@TestMetadata("nullableArgumentToNonNullParameterSimple.kt")
public void testNullableArgumentToNonNullParameterSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/nullableTypes/nullableArgumentToNonNullParameterSimple.kt");
doTest(fileName);
}
@TestMetadata("redundantNullable.kt")
public void testRedundantNullable() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/nullableTypes/redundantNullable.kt");