For-loop now cannot use extension iterator on a nullable #KT-7428 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-01-21 21:04:26 +03:00
parent 218dd41a08
commit 850dc89b38
9 changed files with 79 additions and 13 deletions
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.Severity;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
@@ -35,11 +36,11 @@ import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeUtils; import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.expressions.KotlinTypeInfo; import org.jetbrains.kotlin.types.expressions.KotlinTypeInfo;
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt; import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt;
import org.jetbrains.kotlin.util.slicedMap.MutableSlicedMap; import org.jetbrains.kotlin.util.slicedMap.*;
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice;
import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
import java.util.Collection; import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
import static org.jetbrains.kotlin.diagnostics.Errors.AMBIGUOUS_LABEL; import static org.jetbrains.kotlin.diagnostics.Errors.AMBIGUOUS_LABEL;
import static org.jetbrains.kotlin.resolve.BindingContext.*; import static org.jetbrains.kotlin.resolve.BindingContext.*;
@@ -222,7 +223,7 @@ public class BindingContextUtils {
map.forEach(new Function3<WritableSlice, Object, Object, Void>() { map.forEach(new Function3<WritableSlice, Object, Object, Void>() {
@Override @Override
public Void invoke(WritableSlice slice, Object key, Object value) { public Void invoke(WritableSlice slice, Object key, Object value) {
if (filter == null || filter.accept(slice, key)) { if (filter == null || filter.accept(slice, null, key)) {
trace.record(slice, key, value); trace.record(slice, key, value);
} }
@@ -233,7 +234,7 @@ public class BindingContextUtils {
if (!commitDiagnostics) return; if (!commitDiagnostics) return;
for (Diagnostic diagnostic : diagnostics.getOwnDiagnostics()) { for (Diagnostic diagnostic : diagnostics.getOwnDiagnostics()) {
if (filter == null || filter.accept(null, diagnostic.getPsiElement())) { if (filter == null || filter.accept(null, diagnostic, diagnostic.getPsiElement())) {
trace.report(diagnostic); trace.report(diagnostic);
} }
} }
@@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.rendering.Renderers; import org.jetbrains.kotlin.diagnostics.rendering.Renderers;
import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
@@ -333,7 +334,7 @@ public class DelegatedPropertyResolver {
dataFlowInfo, traceToResolveDelegatedProperty); dataFlowInfo, traceToResolveDelegatedProperty);
traceToResolveDelegatedProperty.commit(new TraceEntryFilter() { traceToResolveDelegatedProperty.commit(new TraceEntryFilter() {
@Override @Override
public boolean accept(@Nullable WritableSlice<?, ?> slice, Object key) { public boolean accept(@Nullable WritableSlice<?, ?> slice, @Nullable Diagnostic diagnostic, Object key) {
return slice != CONSTRAINT_SYSTEM_COMPLETER; return slice != CONSTRAINT_SYSTEM_COMPLETER;
} }
}, true); }, true);
@@ -17,8 +17,9 @@
package org.jetbrains.kotlin.resolve; package org.jetbrains.kotlin.resolve;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.util.slicedMap.WritableSlice; import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
public interface TraceEntryFilter { public interface TraceEntryFilter {
boolean accept(@Nullable WritableSlice<?, ?> slice, Object key); boolean accept(@Nullable WritableSlice<?, ?> slice, @Nullable Diagnostic diagnostic, Object key);
} }
@@ -1178,7 +1178,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
traceInterpretingRightAsNullableAny.commit(new TraceEntryFilter() { traceInterpretingRightAsNullableAny.commit(new TraceEntryFilter() {
@Override @Override
public boolean accept(@Nullable WritableSlice<?, ?> slice, Object key) { public boolean accept(@Nullable WritableSlice<?, ?> slice, @Nullable Diagnostic diagnostic, Object key) {
// the type of the right (and sometimes left) expression isn't 'Any?' actually // the type of the right (and sometimes left) expression isn't 'Any?' actually
if ((key == right || key == left) && slice == EXPRESSION_TYPE_INFO) return false; if ((key == right || key == left) && slice == EXPRESSION_TYPE_INFO) return false;
@@ -18,11 +18,14 @@ package org.jetbrains.kotlin.types.expressions
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.Call import org.jetbrains.kotlin.psi.Call
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.resolve.BindingContextUtils
import org.jetbrains.kotlin.resolve.TemporaryBindingTrace import org.jetbrains.kotlin.resolve.TemporaryBindingTrace
import org.jetbrains.kotlin.resolve.calls.CallResolver import org.jetbrains.kotlin.resolve.calls.CallResolver
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
@@ -75,17 +78,28 @@ class FakeCallResolver(
context: ExpressionTypingContext, context: ExpressionTypingContext,
valueArguments: List<KtExpression>, valueArguments: List<KtExpression>,
name: Name, name: Name,
callElement: KtExpression? callElement: KtExpression?,
iteratorCheck: Boolean = false
): Pair<Call, OverloadResolutionResults<FunctionDescriptor>> { ): Pair<Call, OverloadResolutionResults<FunctionDescriptor>> {
val fakeTrace = TemporaryBindingTrace.create(context.trace, "trace to resolve fake call for", name) val fakeTrace = TemporaryBindingTrace.create(context.trace, "trace to resolve fake call for", name)
val fakeBindingTrace = context.replaceBindingTrace(fakeTrace) val fakeBindingTrace = context.replaceBindingTrace(fakeTrace)
return makeAndResolveFakeCallInContext(receiver, fakeBindingTrace, valueArguments, name, callElement) { fake -> var hasUnreportedIteratorError = false
fakeTrace.commit({ slice, key -> val result = makeAndResolveFakeCallInContext(receiver, fakeBindingTrace, valueArguments, name, callElement) { fake ->
fakeTrace.commit({ slice, diagnostic, key ->
// excluding all entries related to fake expression // excluding all entries related to fake expression
key != fake // convert all errors on this expression to ITERATOR_MISSING on callElement
val isFakeKey = key == fake
if (iteratorCheck && diagnostic?.severity == Severity.ERROR && isFakeKey) {
hasUnreportedIteratorError = true
}
!isFakeKey
}, true) }, true)
} }
if (hasUnreportedIteratorError && callElement != null) {
context.trace.report(Errors.ITERATOR_MISSING.on(callElement))
}
return result
} }
@JvmOverloads fun makeAndResolveFakeCallInContext( @JvmOverloads fun makeAndResolveFakeCallInContext(
@@ -67,7 +67,7 @@ public class ForLoopConventionsChecker {
Name iterator = Name.identifier("iterator"); Name iterator = Name.identifier("iterator");
Pair<Call, OverloadResolutionResults<FunctionDescriptor>> calls = Pair<Call, OverloadResolutionResults<FunctionDescriptor>> calls =
fakeCallResolver.makeAndResolveFakeCall(loopRange, context, Collections.<KtExpression>emptyList(), iterator, fakeCallResolver.makeAndResolveFakeCall(loopRange, context, Collections.<KtExpression>emptyList(), iterator,
loopRange.getExpression()); loopRange.getExpression(), true);
OverloadResolutionResults<FunctionDescriptor> iteratorResolutionResults = calls.getSecond(); OverloadResolutionResults<FunctionDescriptor> iteratorResolutionResults = calls.getSecond();
if (iteratorResolutionResults.isSuccess()) { if (iteratorResolutionResults.isSuccess()) {
@@ -0,0 +1,21 @@
// See also KT-7428
class Container<K>(val k: K)
// iterator() must be an extension, otherwise code will not compile
operator fun <K> Container<K>.iterator(): Iterator<K> = null!!
fun test() {
val container: Container<String>? = null
// Error
container<!UNSAFE_CALL!>.<!>iterator()
// for extension iterator, this code compiles, but should not
for (s in <!ITERATOR_MISSING!>container<!>) {}
}
class OtherContainer<K>(val k: K) {
operator fun iterator(): Iterator<K> = null!!
}
fun test2() {
val other: OtherContainer<String>? = null
// Error
for (s in <!ITERATOR_MISSING!>other<!>) {}
}
@@ -0,0 +1,22 @@
package
public fun test(): kotlin.Unit
public fun test2(): kotlin.Unit
public operator fun </*0*/ K> Container<K>.iterator(): kotlin.collections.Iterator<K>
public final class Container</*0*/ K> {
public constructor Container</*0*/ K>(/*0*/ k: K)
public final val k: K
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 OtherContainer</*0*/ K> {
public constructor OtherContainer</*0*/ K>(/*0*/ k: K)
public final val k: K
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 final operator fun iterator(): kotlin.collections.Iterator<K>
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -3330,6 +3330,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("ForLoopWithExtensionIteratorOnNullable.kt")
public void testForLoopWithExtensionIteratorOnNullable() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/ForLoopWithExtensionIteratorOnNullable.kt");
doTest(fileName);
}
@TestMetadata("forLoopWithNullableRange.kt") @TestMetadata("forLoopWithNullableRange.kt")
public void testForLoopWithNullableRange() throws Exception { public void testForLoopWithNullableRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/forLoopWithNullableRange.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/forLoopWithNullableRange.kt");