From 850dc89b383aaffb2532d93d9fa8a5234e901c6e Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Thu, 21 Jan 2016 21:04:26 +0300 Subject: [PATCH] For-loop now cannot use extension iterator on a nullable #KT-7428 Fixed --- .../kotlin/resolve/BindingContextUtils.java | 11 +++++----- .../resolve/DelegatedPropertyResolver.java | 3 ++- .../kotlin/resolve/TraceEntryFilter.java | 3 ++- .../BasicExpressionTypingVisitor.java | 2 +- .../types/expressions/FakeCallResolver.kt | 22 +++++++++++++++---- .../ForLoopConventionsChecker.java | 2 +- .../ForLoopWithExtensionIteratorOnNullable.kt | 21 ++++++++++++++++++ ...ForLoopWithExtensionIteratorOnNullable.txt | 22 +++++++++++++++++++ .../checkers/DiagnosticsTestGenerated.java | 6 +++++ 9 files changed, 79 insertions(+), 13 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/controlStructures/ForLoopWithExtensionIteratorOnNullable.kt create mode 100644 compiler/testData/diagnostics/tests/controlStructures/ForLoopWithExtensionIteratorOnNullable.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContextUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContextUtils.java index 7e22647a2d6..d2cf7a0bf38 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContextUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContextUtils.java @@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.diagnostics.Diagnostic; +import org.jetbrains.kotlin.diagnostics.Severity; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; 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.expressions.KotlinTypeInfo; import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt; -import org.jetbrains.kotlin.util.slicedMap.MutableSlicedMap; -import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice; -import org.jetbrains.kotlin.util.slicedMap.WritableSlice; +import org.jetbrains.kotlin.util.slicedMap.*; 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.resolve.BindingContext.*; @@ -222,7 +223,7 @@ public class BindingContextUtils { map.forEach(new Function3() { @Override 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); } @@ -233,7 +234,7 @@ public class BindingContextUtils { if (!commitDiagnostics) return; 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); } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DelegatedPropertyResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DelegatedPropertyResolver.java index b297cd15d34..2720d14926c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DelegatedPropertyResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DelegatedPropertyResolver.java @@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.*; +import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.diagnostics.rendering.Renderers; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; @@ -333,7 +334,7 @@ public class DelegatedPropertyResolver { dataFlowInfo, traceToResolveDelegatedProperty); traceToResolveDelegatedProperty.commit(new TraceEntryFilter() { @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; } }, true); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TraceEntryFilter.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TraceEntryFilter.java index 914ebf18234..2e5a0577c40 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TraceEntryFilter.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TraceEntryFilter.java @@ -17,8 +17,9 @@ package org.jetbrains.kotlin.resolve; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.util.slicedMap.WritableSlice; public interface TraceEntryFilter { - boolean accept(@Nullable WritableSlice slice, Object key); + boolean accept(@Nullable WritableSlice slice, @Nullable Diagnostic diagnostic, Object key); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index 2b12843fa0c..0533d81c60e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -1178,7 +1178,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { traceInterpretingRightAsNullableAny.commit(new TraceEntryFilter() { @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 if ((key == right || key == left) && slice == EXPRESSION_TYPE_INFO) return false; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FakeCallResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FakeCallResolver.kt index 66f54ffbdc4..8b077265e4b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FakeCallResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FakeCallResolver.kt @@ -18,11 +18,14 @@ package org.jetbrains.kotlin.types.expressions import com.intellij.openapi.project.Project 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.psi.Call import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.resolve.BindingContextUtils import org.jetbrains.kotlin.resolve.TemporaryBindingTrace import org.jetbrains.kotlin.resolve.calls.CallResolver import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults @@ -75,17 +78,28 @@ class FakeCallResolver( context: ExpressionTypingContext, valueArguments: List, name: Name, - callElement: KtExpression? + callElement: KtExpression?, + iteratorCheck: Boolean = false ): Pair> { val fakeTrace = TemporaryBindingTrace.create(context.trace, "trace to resolve fake call for", name) val fakeBindingTrace = context.replaceBindingTrace(fakeTrace) - return makeAndResolveFakeCallInContext(receiver, fakeBindingTrace, valueArguments, name, callElement) { fake -> - fakeTrace.commit({ slice, key -> + var hasUnreportedIteratorError = false + val result = makeAndResolveFakeCallInContext(receiver, fakeBindingTrace, valueArguments, name, callElement) { fake -> + fakeTrace.commit({ slice, diagnostic, key -> // 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) } + if (hasUnreportedIteratorError && callElement != null) { + context.trace.report(Errors.ITERATOR_MISSING.on(callElement)) + } + return result } @JvmOverloads fun makeAndResolveFakeCallInContext( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ForLoopConventionsChecker.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ForLoopConventionsChecker.java index 107ccd86eb2..b72159babf5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ForLoopConventionsChecker.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ForLoopConventionsChecker.java @@ -67,7 +67,7 @@ public class ForLoopConventionsChecker { Name iterator = Name.identifier("iterator"); Pair> calls = fakeCallResolver.makeAndResolveFakeCall(loopRange, context, Collections.emptyList(), iterator, - loopRange.getExpression()); + loopRange.getExpression(), true); OverloadResolutionResults iteratorResolutionResults = calls.getSecond(); if (iteratorResolutionResults.isSuccess()) { diff --git a/compiler/testData/diagnostics/tests/controlStructures/ForLoopWithExtensionIteratorOnNullable.kt b/compiler/testData/diagnostics/tests/controlStructures/ForLoopWithExtensionIteratorOnNullable.kt new file mode 100644 index 00000000000..f98dcdfb41e --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/ForLoopWithExtensionIteratorOnNullable.kt @@ -0,0 +1,21 @@ +// See also KT-7428 +class Container(val k: K) +// iterator() must be an extension, otherwise code will not compile +operator fun Container.iterator(): Iterator = null!! + +fun test() { + val container: Container? = null + // Error + container.iterator() + // for extension iterator, this code compiles, but should not + for (s in container) {} +} +class OtherContainer(val k: K) { + operator fun iterator(): Iterator = null!! +} + +fun test2() { + val other: OtherContainer? = null + // Error + for (s in other) {} +} diff --git a/compiler/testData/diagnostics/tests/controlStructures/ForLoopWithExtensionIteratorOnNullable.txt b/compiler/testData/diagnostics/tests/controlStructures/ForLoopWithExtensionIteratorOnNullable.txt new file mode 100644 index 00000000000..49fba38727e --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/ForLoopWithExtensionIteratorOnNullable.txt @@ -0,0 +1,22 @@ +package + +public fun test(): kotlin.Unit +public fun test2(): kotlin.Unit +public operator fun Container.iterator(): kotlin.collections.Iterator + +public final class Container { + public constructor Container(/*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 { + public constructor OtherContainer(/*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 + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 710d94bda67..c441a6095cb 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -3330,6 +3330,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { 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") public void testForLoopWithNullableRange() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/forLoopWithNullableRange.kt");