diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JavaTypeAccessibilityChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JavaTypeAccessibilityChecker.kt new file mode 100644 index 00000000000..422bd105870 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JavaTypeAccessibilityChecker.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.resolve.jvm.checkers + +import com.intellij.util.SmartList +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker +import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext +import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue +import org.jetbrains.kotlin.types.KotlinType + + +class JavaTypeAccessibilityChecker : AdditionalTypeChecker { + override fun checkType( + expression: KtExpression, + expressionType: KotlinType, + expressionTypeWithSmartCast: KotlinType, + c: ResolutionContext<*> + ) { + // NB in Kotlin class hierarchy leading to "pathological" type inference is impossible + // due to EXPOSED_SUPER_CLASS & EXPOSED_SUPER_INTERFACE checks. + // To avoid superfluous diagnostics in case of invisible member class and so on, + // we consider only Java classes as possibly inaccessible. + + val inaccessibleTypes = findInaccessibleJavaTypes(expressionType, c) + if (inaccessibleTypes.isNotEmpty()) { + c.trace.report(Errors.INACCESSIBLE_TYPE.on(expression, expressionType, inaccessibleTypes)) + return + } + + if (expressionTypeWithSmartCast != expressionType) { + val inaccessibleTypesWithSmartCast = findInaccessibleJavaTypes(expressionTypeWithSmartCast, c) + if (inaccessibleTypesWithSmartCast.isNotEmpty()) { + c.trace.report(Errors.INACCESSIBLE_TYPE.on(expression, expressionType, inaccessibleTypes)) + } + } + } + + override fun checkReceiver( + receiverParameter: ReceiverParameterDescriptor, + receiverArgument: ReceiverValue, + safeAccess: Boolean, + c: CallResolutionContext<*> + ) {} + + private fun findInaccessibleJavaTypes(type: KotlinType, c: ResolutionContext<*>): List { + val scopeOwner = c.scope.ownerDescriptor + val inaccessibleTypes = SmartList() + findInaccessibleJavaTypesRec(type, scopeOwner, inaccessibleTypes, hashSetOf()) + return inaccessibleTypes + } + + private fun findInaccessibleJavaTypesRec( + type: KotlinType, + scopeOwner: DeclarationDescriptor, + inaccessibleTypes: SmartList, + visitedTypeConstructors: MutableSet + ) { + val typeConstructor = type.constructor.declarationDescriptor + if (typeConstructor is ClassDescriptor) { + if (visitedTypeConstructors.contains(typeConstructor)) return + visitedTypeConstructors.add(typeConstructor) + + if (typeConstructor is JavaClassDescriptor && !Visibilities.isVisibleWithIrrelevantReceiver(typeConstructor, scopeOwner)) { + inaccessibleTypes.add(type) + } + for (typeProjection in type.arguments) { + findInaccessibleJavaTypesRec(typeProjection.type, scopeOwner, inaccessibleTypes, visitedTypeConstructors) + } + } + } + +} \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index a80f0ae44f4..1243b0c4023 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -57,7 +57,8 @@ object JvmPlatformConfigurator : PlatformConfigurator( additionalTypeCheckers = listOf( JavaNullabilityWarningsChecker(), RuntimeAssertionsTypeChecker, - JavaGenericVarianceViolationTypeChecker + JavaGenericVarianceViolationTypeChecker, + JavaTypeAccessibilityChecker() ), additionalSymbolUsageValidators = listOf(), diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 101767a0cc7..0ef9db5d013 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -83,6 +83,8 @@ public interface Errors { DiagnosticFactory2 EXPOSED_SUPER_CLASS = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 EXPOSED_SUPER_INTERFACE = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2> INACCESSIBLE_TYPE = DiagnosticFactory2.create(ERROR); + DiagnosticFactory1> PLATFORM_CLASS_MAPPED_TO_KOTLIN = DiagnosticFactory1.create(WARNING); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index d009db238e4..c809a2fe097 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -122,6 +122,8 @@ public class DefaultErrorMessages { MAP.put(EXPOSED_SUPER_CLASS, "Subclass effective visibility ''{0}'' should be the same or less permissive than its superclass effective visibility ''{1}''", TO_STRING, TO_STRING); MAP.put(EXPOSED_SUPER_INTERFACE, "Sub-interface effective visibility ''{0}'' should be the same or less permissive than its super-interface effective visibility ''{1}''", TO_STRING, TO_STRING); + MAP.put(INACCESSIBLE_TYPE, "Type {0} is inaccessible in this context due to: {1}", RENDER_TYPE, RENDER_COLLECTION_OF_TYPES); + MAP.put(REDECLARATION, "Redeclaration: {0}", STRING); MAP.put(NAME_SHADOWING, "Name shadowed: {0}", STRING); MAP.put(ACCESSOR_PARAMETER_NAME_SHADOWING, "Accessor parameter name 'field' is shadowed by backing field variable"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java index 4726eebc6f0..1be4e42e48f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java @@ -181,8 +181,10 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { } } // If break or continue was possible, take condition check info as the jump info - return TypeInfoFactoryKt.createTypeInfo(resultType, resultDataFlowInfo, loopBreakContinuePossible, - loopBreakContinuePossibleInCondition ? context.dataFlowInfo : conditionDataFlowInfo); + return TypeInfoFactoryKt.createTypeInfo( + components.dataFlowAnalyzer.checkType(resultType, ifExpression, contextWithExpectedType), + resultDataFlowInfo, loopBreakContinuePossible, + loopBreakContinuePossibleInCondition ? context.dataFlowInfo : conditionDataFlowInfo); } @NotNull diff --git a/compiler/testData/diagnostics/tests/exposed/inaccessibleType.kt b/compiler/testData/diagnostics/tests/exposed/inaccessibleType.kt new file mode 100644 index 00000000000..c74cc2020e7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/exposed/inaccessibleType.kt @@ -0,0 +1,88 @@ +// !DIAGNOSTICS: -USELESS_CAST -UNUSED_PARAMETER -UNUSED_VARIABLE + +// FILE: j/Base.java +package j; +public interface Base { + void foo(); +} + +// FILE: j/Impl.java +package j; + +/* package */ abstract class Impl implements Base { + public void foo() {} +} + +// FILE: j/Derived1.java +package j; + +public class Derived1 extends Impl {} + +// FILE: j/Derived2.java +package j; + +public class Derived2 extends Impl {} + +// FILE: k/Client.kt +package k + +import j.* + +val d1 = Derived1() +val d2 = Derived2() + +fun select(x1: T, x2: T) = x1 +fun selectn(vararg xx: T) = xx[0] +fun foo(x: T) = x.foo() +fun listOf2(x1: T, x2: T): List = null!! +fun arrayOf2(x1: T, x2: T): Array = null!! + +fun test() { + val test1: Base = if (true) d1 else d2 + + val test2 = if (true) d1 else d2 + + val test3 = when { + true -> d1 + else -> d2 + } + + val test4: Base = when { + true -> d1 + else -> d2 + } + + val test5 = select(d1, d2) + + val test6 = select(d1, d2) + + val test7 = select(d1 as Base, d2) + + val test8 = selectn(d1, d2) + + val test9 = selectn(d1, d2) + + val test10 = listOf2(d1, d2) + + val test11: List = listOf2(d1, d2) + // NB Inferred type is List because List is covariant. + + val test12 = listOf2(d1, d2) + + val test13 = arrayOf2(d1, d2) + + val test14: Array = arrayOf2(d1, d2) + // NB Inferred type is Array because Array is invariant. + + val test15 = arrayOf2(d1, d2) + + for (test16 in listOf2(d1, d2)) {} +} + +fun testOkInJava() { + // The following is Ok in Java, but is an error in Kotlin. + // TODO do not generate unneeded CHECKCASTs. + // TODO do not report INACCESSIBLE_TYPE for corresponding cases. + select(d1, d2) + foo(select(d1, d2)) +} diff --git a/compiler/testData/diagnostics/tests/exposed/inaccessibleType.txt b/compiler/testData/diagnostics/tests/exposed/inaccessibleType.txt new file mode 100644 index 00000000000..be547868ba7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/exposed/inaccessibleType.txt @@ -0,0 +1,13 @@ +package + +package k { + public val d1: j.Derived1 + public val d2: j.Derived2 + public fun arrayOf2(/*0*/ x1: T, /*1*/ x2: T): kotlin.Array + public fun foo(/*0*/ x: T): kotlin.Unit + public fun listOf2(/*0*/ x1: T, /*1*/ x2: T): kotlin.collections.List + public fun select(/*0*/ x1: T, /*1*/ x2: T): T + public fun selectn(/*0*/ vararg xx: T /*kotlin.Array*/): T + public fun test(): kotlin.Unit + public fun testOkInJava(): kotlin.Unit +} diff --git a/compiler/testData/diagnostics/tests/exposed/packagePrivate.kt b/compiler/testData/diagnostics/tests/exposed/packagePrivate.kt index 7d66d3a7cd7..ba18543da2d 100644 --- a/compiler/testData/diagnostics/tests/exposed/packagePrivate.kt +++ b/compiler/testData/diagnostics/tests/exposed/packagePrivate.kt @@ -30,6 +30,5 @@ package other import test.My class Your { - // Ok but dangerous: internal vs package-private in different package - internal fun bar() = My.foo() + internal fun bar() = My.foo() } diff --git a/compiler/testData/diagnostics/tests/j+k/packageVisibility.kt b/compiler/testData/diagnostics/tests/j+k/packageVisibility.kt index 3f614cbca9e..0dd93379278 100644 --- a/compiler/testData/diagnostics/tests/j+k/packageVisibility.kt +++ b/compiler/testData/diagnostics/tests/j+k/packageVisibility.kt @@ -26,19 +26,19 @@ package b import a.MyJavaClass -val mc1 = MyJavaClass() +val mc1 = MyJavaClass() val x = MyJavaClass.staticMethod() val y = MyJavaClass.NestedClass.staticMethodOfNested() -val z = MyJavaClass.NestedClass() +val z = MyJavaClass.NestedClass() //FILE: c.kt package a.c import a.MyJavaClass -val mc1 = MyJavaClass() +val mc1 = MyJavaClass() val x = MyJavaClass.staticMethod() val y = MyJavaClass.NestedClass.staticMethodOfNested() -val z = MyJavaClass.NestedClass() \ No newline at end of file +val z = MyJavaClass.NestedClass() \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index f874dc1704d..92894039103 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -6180,6 +6180,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("inaccessibleType.kt") + public void testInaccessibleType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/exposed/inaccessibleType.kt"); + doTest(fileName); + } + @TestMetadata("internal.kt") public void testInternal() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/exposed/internal.kt");