diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt new file mode 100644 index 00000000000..f42eb5e5674 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2010-2015 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.cfg + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode +import org.jetbrains.kotlin.cfg.pseudocode.instructions.KtElementInstruction +import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction +import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind +import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.ReadValueInstruction +import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder +import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.types.expressions.OperatorConventions + +sealed class LeakingThisDescriptor(val classOrObject: KtClassOrObject) { + class PropertyIsNull(val property: PropertyDescriptor, classOrObject: KtClassOrObject) : LeakingThisDescriptor(classOrObject) +} + +class ConstructorConsistencyChecker private constructor( + private val classOrObject: KtClassOrObject, + private val classDescriptor: ClassDescriptor, + private val trace: BindingTrace, + private val pseudocode: Pseudocode, + private val variablesData: PseudocodeVariablesData +) { + + private fun safeThisUsage(expression: KtThisExpression): Boolean { + val referenceDescriptor = trace.get(BindingContext.REFERENCE_TARGET, expression.instanceReference) + if (referenceDescriptor != classDescriptor) return true + val parent = expression.parent + return when (parent) { + is KtQualifiedExpression -> parent.selectorExpression is KtSimpleNameExpression + is KtBinaryExpression -> OperatorConventions.IDENTITY_EQUALS_OPERATIONS.contains(parent.operationToken) + else -> false + } + } + + private fun safeCallUsage(expression: KtCallExpression): Boolean { + val callee = expression.calleeExpression + if (callee is KtReferenceExpression) { + val descriptor = trace.get(BindingContext.REFERENCE_TARGET, callee) + if (descriptor is FunctionDescriptor) { + val containingDescriptor = descriptor.containingDeclaration + if (containingDescriptor != classDescriptor) return true + } + } + return false + } + + fun check() { + // List of properties to initialize + val propertyDescriptors = variablesData.getDeclaredVariables(pseudocode, false) + .filterIsInstance() + .filter { trace.get(BindingContext.BACKING_FIELD_REQUIRED, it) == true } + pseudocode.traverse( + TraversalOrder.FORWARD, variablesData.variableInitializers, { instruction, enterData, exitData -> + + fun firstUninitializedNotNullProperty() = propertyDescriptors.firstOrNull { + !it.type.isMarkedNullable && !KotlinBuiltIns.isPrimitiveType(it.type) && + !it.isLateInit && !(enterData[it]?.definitelyInitialized() ?: false) + } + + fun handleLeakingThis(expression: KtExpression) { + val uninitializedProperty = firstUninitializedNotNullProperty() + if (uninitializedProperty != null) { + trace.record(BindingContext.LEAKING_THIS, target(expression), + LeakingThisDescriptor.PropertyIsNull(uninitializedProperty, classOrObject)) + } + } + + if (instruction.owner != pseudocode) { + return@traverse + } + + if (instruction is KtElementInstruction) { + val element = instruction.element + when (instruction) { + is ReadValueInstruction -> + if (element is KtThisExpression) { + if (!safeThisUsage(element)) { + handleLeakingThis(element) + } + } + is MagicInstruction -> + if (instruction.kind == MagicKind.IMPLICIT_RECEIVER && element is KtCallExpression) { + if (!safeCallUsage(element)) { + handleLeakingThis(element) + } + } + } + } + }) + } + + companion object { + + @JvmStatic + fun check( + constructor: KtSecondaryConstructor, + trace: BindingTrace, + pseudocode: Pseudocode, + pseudocodeVariablesData: PseudocodeVariablesData + ) = check(constructor.getContainingClassOrObject(), trace, pseudocode, pseudocodeVariablesData) + + @JvmStatic + fun check( + classOrObject: KtClassOrObject, + trace: BindingTrace, + pseudocode: Pseudocode, + pseudocodeVariablesData: PseudocodeVariablesData + ) { + val classDescriptor = trace.get(BindingContext.CLASS, classOrObject) ?: return + ConstructorConsistencyChecker(classOrObject, classDescriptor, trace, pseudocode, pseudocodeVariablesData).check() + } + + private fun target(expression: KtExpression): KtExpression = when (expression) { + is KtThisExpression -> { + val selectorOrThis = (expression.parent as? KtQualifiedExpression)?.let { + if (it.receiverExpression === expression) it.selectorExpression else null + } ?: expression + if (selectorOrThis === expression) selectorOrThis else target(selectorOrThis) + } + is KtCallExpression -> expression.let { it.calleeExpression ?: it } + else -> expression + } + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java index 32cdb678d2f..b756df75ba7 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java @@ -125,6 +125,8 @@ public class ControlFlowInformationProvider { checkIfExpressions(); checkWhenExpressions(); + + checkConstructorConsistency(); } public void checkFunction(@Nullable KotlinType expectedReturnType) { @@ -923,6 +925,15 @@ public class ControlFlowInformationProvider { ); } + private void checkConstructorConsistency() { + if (subroutine instanceof KtClassOrObject) { + ConstructorConsistencyChecker.check((KtClassOrObject) subroutine, trace, pseudocode, pseudocodeVariablesData); + } + else if (subroutine instanceof KtSecondaryConstructor) { + ConstructorConsistencyChecker.check((KtSecondaryConstructor) subroutine, trace, pseudocode, pseudocodeVariablesData); + } + } + //////////////////////////////////////////////////////////////////////////////// // Tail calls diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerTestUtil.java b/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerTestUtil.java index 81df046e67e..73957740d6d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerTestUtil.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerTestUtil.java @@ -166,6 +166,11 @@ public class CheckerTestUtil { debugAnnotations.add(new DebugInfoDiagnostic(expression, DebugInfoDiagnosticFactory.CONSTANT)); } } + for (KtExpression expression : bindingContext.getSliceContents(BindingContext.LEAKING_THIS).keySet()) { + if (PsiTreeUtil.isAncestor(root, expression, false)) { + debugAnnotations.add(new DebugInfoDiagnostic(expression, DebugInfoDiagnosticFactory.LEAKING_THIS)); + } + } for (KtWhenExpression expression : bindingContext.getSliceContents(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN).keySet()) { if (PsiTreeUtil.isAncestor(root, expression, false)) { debugAnnotations.add(new DebugInfoDiagnostic(expression, DebugInfoDiagnosticFactory.IMPLICIT_EXHAUSTIVE)); @@ -533,6 +538,7 @@ public class CheckerTestUtil { public static final DebugInfoDiagnosticFactory SMARTCAST = new DebugInfoDiagnosticFactory("SMARTCAST"); public static final DebugInfoDiagnosticFactory IMPLICIT_RECEIVER_SMARTCAST = new DebugInfoDiagnosticFactory("IMPLICIT_RECEIVER_SMARTCAST"); public static final DebugInfoDiagnosticFactory CONSTANT = new DebugInfoDiagnosticFactory("CONSTANT"); + public static final DebugInfoDiagnosticFactory LEAKING_THIS = new DebugInfoDiagnosticFactory("LEAKING_THIS"); public static final DebugInfoDiagnosticFactory IMPLICIT_EXHAUSTIVE = new DebugInfoDiagnosticFactory("IMPLICIT_EXHAUSTIVE"); public static final DebugInfoDiagnosticFactory ELEMENT_WITH_ERROR_TYPE = new DebugInfoDiagnosticFactory("ELEMENT_WITH_ERROR_TYPE"); public static final DebugInfoDiagnosticFactory UNRESOLVED_WITH_TARGET = new DebugInfoDiagnosticFactory("UNRESOLVED_WITH_TARGET"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java index 0d18142a02d..6633031d0fa 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.ReadOnly; import org.jetbrains.annotations.TestOnly; +import org.jetbrains.kotlin.cfg.LeakingThisDescriptor; import org.jetbrains.kotlin.cfg.TailRecursionKind; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor; @@ -97,6 +98,7 @@ public interface BindingContext { WritableSlice EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice(DO_NOTHING); WritableSlice EXPECTED_RETURN_TYPE = new BasicWritableSlice(DO_NOTHING); WritableSlice DATAFLOW_INFO_AFTER_CONDITION = Slices.createSimpleSlice(); + WritableSlice LEAKING_THIS = Slices.createSimpleSlice(); /** * A qualifier corresponds to a receiver expression (if any). For 'A.B' qualifier is recorded for 'A'. diff --git a/compiler/testData/diagnostics/tests/RecursiveResolve.kt b/compiler/testData/diagnostics/tests/RecursiveResolve.kt index e8a9c181957..b3a3c968c78 100644 --- a/compiler/testData/diagnostics/tests/RecursiveResolve.kt +++ b/compiler/testData/diagnostics/tests/RecursiveResolve.kt @@ -1,6 +1,6 @@ class Test(foo: Any?, bar: Any?) { - val foo = foo ?: this - private val bar = bar ?: this + val foo = foo ?: this + private val bar = bar ?: this private val bas = bas() val bas2 = bas2() @@ -15,8 +15,8 @@ class Test(foo: Any?, bar: Any?) { // KT-6413 Typechecker recursive problem when class have non-invariant generic parameters class Test2(foo: Any?, bar: Any?) { - val foo = foo ?: this - private val bar = bar ?: this + val foo = foo ?: this + private val bar = bar ?: this private val bas = bas() val bas2 = bas2() @@ -30,8 +30,8 @@ class Test2(foo: Any?, bar: Any?) { } class Test3(foo: Any?, bar: Any?) { - val foo = foo ?: this - private val bar = bar ?: this + val foo = foo ?: this + private val bar = bar ?: this private val bas = bas() val bas2 = bas2() @@ -45,8 +45,8 @@ class Test3(foo: Any?, bar: Any?) { } class Test4(foo: Any?, bar: Any?) { - val foo = foo ?: this - private val bar = bar ?: this + val foo = foo ?: this + private val bar = bar ?: this private val bas = bas() val bas2 = bas2() @@ -60,8 +60,8 @@ class Test4(foo: Any?, bar: Any?) { } class Test5(foo: Any?, bar: Any?) { - val foo = foo ?: this - private val bar = bar ?: this + val foo = foo ?: this + private val bar = bar ?: this private val bas: Int = bas() val bas2 = bas2() diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/afterInitialization.kt b/compiler/testData/diagnostics/tests/constructorConsistency/afterInitialization.kt new file mode 100644 index 00000000000..269ed78f62b --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/afterInitialization.kt @@ -0,0 +1,6 @@ +class My(val x: Int) { + val y: Int = x + 3 + val z: Int? = foo() + + fun foo() = if (x >= 0) x else if (y >= 0) y else null +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/afterInitialization.txt b/compiler/testData/diagnostics/tests/constructorConsistency/afterInitialization.txt new file mode 100644 index 00000000000..a0337f755f5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/afterInitialization.txt @@ -0,0 +1,12 @@ +package + +public final class My { + public constructor My(/*0*/ x: kotlin.Int) + public final val x: kotlin.Int + public final val y: kotlin.Int + public final val z: kotlin.Int? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int? + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/aliencall.kt b/compiler/testData/diagnostics/tests/constructorConsistency/aliencall.kt new file mode 100644 index 00000000000..d3ca6dd272d --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/aliencall.kt @@ -0,0 +1,29 @@ +fun foo() = 42 + +class Your { + fun bar() = 13 +} + +class My { + val your = Your() + + val x = foo() + + val y = Your().bar() + + val z = your.bar() + + // This extension function also can use our properties, + // so the call is also dangerous + val w = your.gav() + + val v = Your().gav() + + val t = your.other() + + val r = Your().other() + + fun Your.gav() = if (your.bar() == 0) t else r +} + +fun Your.other() = "3" \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/aliencall.txt b/compiler/testData/diagnostics/tests/constructorConsistency/aliencall.txt new file mode 100644 index 00000000000..6c47225da87 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/aliencall.txt @@ -0,0 +1,28 @@ +package + +public fun foo(): kotlin.Int +public fun Your.other(): kotlin.String + +public final class My { + public constructor My() + public final val r: kotlin.String + public final val t: kotlin.String + public final val v: kotlin.String + public final val w: kotlin.String + public final val x: kotlin.Int + public final val y: kotlin.Int + public final val your: Your + public final val z: kotlin.Int + 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 fun Your.gav(): kotlin.String +} + +public final class Your { + public constructor Your() + public final fun bar(): kotlin.Int + 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 +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/assignment.kt b/compiler/testData/diagnostics/tests/constructorConsistency/assignment.kt new file mode 100644 index 00000000000..2c65dc05bac --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/assignment.kt @@ -0,0 +1,11 @@ +class My { + val x: String + + constructor() { + val temp = this + x = bar(temp) + } + +} + +fun bar(arg: My) = arg.x diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/assignment.txt b/compiler/testData/diagnostics/tests/constructorConsistency/assignment.txt new file mode 100644 index 00000000000..928506c0714 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/assignment.txt @@ -0,0 +1,11 @@ +package + +public fun bar(/*0*/ arg: My): kotlin.String + +public final class My { + public constructor My() + public final val x: kotlin.String + 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 +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/basic.kt b/compiler/testData/diagnostics/tests/constructorConsistency/basic.kt new file mode 100644 index 00000000000..3604dc9041c --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/basic.kt @@ -0,0 +1,13 @@ +class My { + val x: String + + constructor() { + val y = bar(this) + val z = foo() + x = "$y$z" + } + + fun foo() = x +} + +fun bar(arg: My) = arg.x diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/basic.txt b/compiler/testData/diagnostics/tests/constructorConsistency/basic.txt new file mode 100644 index 00000000000..08d69380a0e --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/basic.txt @@ -0,0 +1,12 @@ +package + +public fun bar(/*0*/ arg: My): kotlin.String + +public final class My { + public constructor My() + public final val x: kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/companion.kt b/compiler/testData/diagnostics/tests/constructorConsistency/companion.kt new file mode 100644 index 00000000000..ecbb2fc0156 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/companion.kt @@ -0,0 +1,19 @@ +class My { + + val x = foo() + + val w = bar() + + fun foo() = 0 + + companion object { + + val y = foo() + + val u = bar() + + val z: String? = bar() + + fun bar() = "1" + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/companion.txt b/compiler/testData/diagnostics/tests/constructorConsistency/companion.txt new file mode 100644 index 00000000000..9f642478868 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/companion.txt @@ -0,0 +1,22 @@ +package + +public final class My { + public constructor My() + public final val w: kotlin.String + public final val x: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int + 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 final val u: kotlin.String + public final val y: [ERROR : Type for foo()] + public final val z: kotlin.String? + public final fun bar(): kotlin.String + 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 + } +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/comparison.kt b/compiler/testData/diagnostics/tests/constructorConsistency/comparison.kt new file mode 100644 index 00000000000..f6a3e3dbaee --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/comparison.kt @@ -0,0 +1,10 @@ +val instance = My() + +class My { + val equalsInstance = (this == instance) + + val isInstance = if (this === instance) "true" else "false" + + override fun equals(other: Any?) = + other is My && isInstance.hashCode() == other.isInstance.hashCode() +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/comparison.txt b/compiler/testData/diagnostics/tests/constructorConsistency/comparison.txt new file mode 100644 index 00000000000..5d796d20a62 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/comparison.txt @@ -0,0 +1,12 @@ +package + +public val instance: My + +public final class My { + public constructor My() + public final val equalsInstance: kotlin.Boolean + public final val isInstance: kotlin.String + public open override /*1*/ 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 +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/delegate.kt b/compiler/testData/diagnostics/tests/constructorConsistency/delegate.kt new file mode 100644 index 00000000000..87b106a035c --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/delegate.kt @@ -0,0 +1,13 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +import kotlin.reflect.KProperty + +class Delegate(val x: Int) { + operator fun getValue(t: Any?, p: KProperty<*>): Int = x +} + +class My { + val x: Int by Delegate(this.foo()) + + fun foo(): Int = x +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/delegate.txt b/compiler/testData/diagnostics/tests/constructorConsistency/delegate.txt new file mode 100644 index 00000000000..ede3056eba1 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/delegate.txt @@ -0,0 +1,19 @@ +package + +public final class Delegate { + public constructor Delegate(/*0*/ x: kotlin.Int) + public final val x: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun getValue(/*0*/ t: kotlin.Any?, /*1*/ p: kotlin.reflect.KProperty<*>): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class My { + public constructor My() + public final val x: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/derived.kt b/compiler/testData/diagnostics/tests/constructorConsistency/derived.kt new file mode 100644 index 00000000000..0e5b8a152c1 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/derived.kt @@ -0,0 +1,11 @@ +open class Base(val x: String) { + fun foo() = bar() + + open fun bar() = -1 +} + +class Derived(x: String): Base(x) { + // It's still dangerous: we're not sure that foo() does not call some open function inside + val y = foo() + val z = x +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/derived.txt b/compiler/testData/diagnostics/tests/constructorConsistency/derived.txt new file mode 100644 index 00000000000..7ca2c4b14a5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/derived.txt @@ -0,0 +1,23 @@ +package + +public open class Base { + public constructor Base(/*0*/ x: kotlin.String) + public final val x: kotlin.String + public open fun bar(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Derived : Base { + public constructor Derived(/*0*/ x: kotlin.String) + public final override /*1*/ /*fake_override*/ val x: kotlin.String + public final val y: kotlin.Int + public final val z: kotlin.String + public open override /*1*/ /*fake_override*/ fun bar(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun foo(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/init.kt b/compiler/testData/diagnostics/tests/constructorConsistency/init.kt new file mode 100644 index 00000000000..11115d5449f --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/init.kt @@ -0,0 +1,9 @@ +class My { + val x: String + + init { + x = foo() + } + + fun foo(): String = x +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/init.txt b/compiler/testData/diagnostics/tests/constructorConsistency/init.txt new file mode 100644 index 00000000000..16ba6c3d937 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/init.txt @@ -0,0 +1,10 @@ +package + +public final class My { + public constructor My() + public final val x: kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/lambdaInObject.kt b/compiler/testData/diagnostics/tests/constructorConsistency/lambdaInObject.kt new file mode 100644 index 00000000000..eb4204ba880 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/lambdaInObject.kt @@ -0,0 +1,18 @@ +interface Wise { + fun doIt(): Int +} + +fun Wise(f: () -> Int) = object: Wise { + override fun doIt() = f() +} + +class My { + // Still dangerous (???), nobogy can guarantee what Wise() will do with this lambda + val x = Wise { foo() } + + val y = 42 + + fun foo() = y + + fun bar() = x.doIt() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/lambdaInObject.txt b/compiler/testData/diagnostics/tests/constructorConsistency/lambdaInObject.txt new file mode 100644 index 00000000000..947b38a0c08 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/lambdaInObject.txt @@ -0,0 +1,21 @@ +package + +public fun Wise(/*0*/ f: () -> kotlin.Int): Wise + +public final class My { + public constructor My() + public final val x: Wise + public final val y: kotlin.Int = 42 + public final fun bar(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface Wise { + public abstract fun doIt(): kotlin.Int + 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 +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/lateInit.kt b/compiler/testData/diagnostics/tests/constructorConsistency/lateInit.kt new file mode 100644 index 00000000000..87b435651e6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/lateInit.kt @@ -0,0 +1,15 @@ +class WithLateInit { + lateinit var x: String + + fun init(xx: String) { + x = xx + } + + init { + // This is obviously a bug, + // but with lateinit we explicitly want it to occur in runtime + use() + } + + fun use() = x +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/lateInit.txt b/compiler/testData/diagnostics/tests/constructorConsistency/lateInit.txt new file mode 100644 index 00000000000..572f378b539 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/lateInit.txt @@ -0,0 +1,11 @@ +package + +public final class WithLateInit { + public constructor WithLateInit() + public final lateinit var x: kotlin.String + 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 fun init(/*0*/ xx: kotlin.String): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public final fun use(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/localObject.kt b/compiler/testData/diagnostics/tests/constructorConsistency/localObject.kt new file mode 100644 index 00000000000..c4f8951c4f0 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/localObject.kt @@ -0,0 +1,19 @@ +open class Wise { + + val x = 1 + + open fun doIt(): Int = 42 +} + +class My { + + fun foo(): Int { + val wise = object: Wise() { + var xx = 1 + override fun doIt() = super.doIt() + bar(this) + xx + } + return wise.doIt() + } +} + +fun bar(wise: Wise): Int = wise.x \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/localObject.txt b/compiler/testData/diagnostics/tests/constructorConsistency/localObject.txt new file mode 100644 index 00000000000..8c913aa8386 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/localObject.txt @@ -0,0 +1,20 @@ +package + +public fun bar(/*0*/ wise: Wise): kotlin.Int + +public final class My { + public constructor My() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class Wise { + public constructor Wise() + public final val x: kotlin.Int = 1 + public open fun doIt(): kotlin.Int + 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 +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/nobacking.kt b/compiler/testData/diagnostics/tests/constructorConsistency/nobacking.kt new file mode 100644 index 00000000000..0d62f81b31f --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/nobacking.kt @@ -0,0 +1,12 @@ +class My { + + val x: Int + get() = 1 + + init { + // x has no backing field, so the call is safe + foo() + } + + fun foo() {} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/nobacking.txt b/compiler/testData/diagnostics/tests/constructorConsistency/nobacking.txt new file mode 100644 index 00000000000..a76bb06eea5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/nobacking.txt @@ -0,0 +1,10 @@ +package + +public final class My { + public constructor My() + public final val x: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/outer.kt b/compiler/testData/diagnostics/tests/constructorConsistency/outer.kt new file mode 100644 index 00000000000..a1027c124db --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/outer.kt @@ -0,0 +1,11 @@ +class Outer { + + fun foo() = 1 + + inner class Inner { + + val x = this@Outer.foo() + + val y = foo() + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/outer.txt b/compiler/testData/diagnostics/tests/constructorConsistency/outer.txt new file mode 100644 index 00000000000..a33eaec67d3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/outer.txt @@ -0,0 +1,18 @@ +package + +public final class Outer { + public constructor Outer() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final inner class Inner { + public constructor Inner() + public final val x: kotlin.Int + public final val y: kotlin.Int + 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 + } +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/property.kt b/compiler/testData/diagnostics/tests/constructorConsistency/property.kt new file mode 100644 index 00000000000..82437ad5124 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/property.kt @@ -0,0 +1,5 @@ +class My(x: String) { + val y: String = foo(x) + + fun foo(x: String) = "$x$y" +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/property.txt b/compiler/testData/diagnostics/tests/constructorConsistency/property.txt new file mode 100644 index 00000000000..6208135a3bb --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/property.txt @@ -0,0 +1,10 @@ +package + +public final class My { + public constructor My(/*0*/ x: kotlin.String) + public final val y: kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(/*0*/ x: kotlin.String): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/propertyAccess.kt b/compiler/testData/diagnostics/tests/constructorConsistency/propertyAccess.kt new file mode 100644 index 00000000000..33054eb8a4c --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/propertyAccess.kt @@ -0,0 +1,7 @@ +class My { + val x: Int + + constructor(x: Int) { + this.x = x + } +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/propertyAccess.txt b/compiler/testData/diagnostics/tests/constructorConsistency/propertyAccess.txt new file mode 100644 index 00000000000..392cfe319da --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/propertyAccess.txt @@ -0,0 +1,9 @@ +package + +public final class My { + public constructor My(/*0*/ x: kotlin.Int) + public final val x: kotlin.Int + 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 +} diff --git a/compiler/testData/diagnostics/tests/duplicateJvmSignature/specialNames/classObjectCopiedFieldObject.kt b/compiler/testData/diagnostics/tests/duplicateJvmSignature/specialNames/classObjectCopiedFieldObject.kt index a95ee845d35..244da9b5eeb 100644 --- a/compiler/testData/diagnostics/tests/duplicateJvmSignature/specialNames/classObjectCopiedFieldObject.kt +++ b/compiler/testData/diagnostics/tests/duplicateJvmSignature/specialNames/classObjectCopiedFieldObject.kt @@ -2,12 +2,12 @@ class B { companion object A { } - val A = this + val A = this } class C { companion object A { - val A = this + val A = this } } diff --git a/compiler/testData/diagnostics/tests/generics/capturedParameters/localClass.kt b/compiler/testData/diagnostics/tests/generics/capturedParameters/localClass.kt index 71aa61d27c7..a15270d8bb9 100644 --- a/compiler/testData/diagnostics/tests/generics/capturedParameters/localClass.kt +++ b/compiler/testData/diagnostics/tests/generics/capturedParameters/localClass.kt @@ -10,8 +10,8 @@ class Q { C() } - private var x = foo()() - private var y = foo()() + private var x = foo()() + private var y = foo()() fun bar() { x = y diff --git a/compiler/testData/diagnostics/tests/generics/capturedParameters/localWithTypeParameter.kt b/compiler/testData/diagnostics/tests/generics/capturedParameters/localWithTypeParameter.kt index a7ff90eb7b9..2eecbdc9062 100644 --- a/compiler/testData/diagnostics/tests/generics/capturedParameters/localWithTypeParameter.kt +++ b/compiler/testData/diagnostics/tests/generics/capturedParameters/localWithTypeParameter.kt @@ -12,7 +12,7 @@ class Q { C() } - private var x = foo()() + private var x = foo()() fun bar() { x.e.checkType { _() } diff --git a/compiler/testData/diagnostics/tests/generics/capturedParameters/objectLiteral.kt b/compiler/testData/diagnostics/tests/generics/capturedParameters/objectLiteral.kt index 5da8b8f602f..37af4b63f8f 100644 --- a/compiler/testData/diagnostics/tests/generics/capturedParameters/objectLiteral.kt +++ b/compiler/testData/diagnostics/tests/generics/capturedParameters/objectLiteral.kt @@ -8,8 +8,8 @@ class Q { val prop: E = magic() } - private var x = foo() - private var y = foo() + private var x = foo() + private var y = foo() fun bar() { x = y diff --git a/compiler/testData/diagnostics/tests/redeclarations/NoRedeclarationForEnumEntriesAndDefaultObjectMembers.kt b/compiler/testData/diagnostics/tests/redeclarations/NoRedeclarationForEnumEntriesAndDefaultObjectMembers.kt index cc2979f1768..aa67faa2766 100644 --- a/compiler/testData/diagnostics/tests/redeclarations/NoRedeclarationForEnumEntriesAndDefaultObjectMembers.kt +++ b/compiler/testData/diagnostics/tests/redeclarations/NoRedeclarationForEnumEntriesAndDefaultObjectMembers.kt @@ -6,6 +6,6 @@ enum class E { companion object { class FIRST - val SECOND = this + val SECOND = this } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/regressions/kt688.kt b/compiler/testData/diagnostics/tests/regressions/kt688.kt index b29056972ba..76ab8a4890b 100644 --- a/compiler/testData/diagnostics/tests/regressions/kt688.kt +++ b/compiler/testData/diagnostics/tests/regressions/kt688.kt @@ -10,5 +10,5 @@ class C() { return x(x(y)) } - val x: B = a({it.b()}, B()) + val x: B = a({it.b()}, B()) } diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/QualifiedThis.kt b/compiler/testData/diagnostics/tests/thisAndSuper/QualifiedThis.kt index bffba2c0093..f5e3a64f79e 100644 --- a/compiler/testData/diagnostics/tests/thisAndSuper/QualifiedThis.kt +++ b/compiler/testData/diagnostics/tests/thisAndSuper/QualifiedThis.kt @@ -6,7 +6,7 @@ class A() { this } - val x = this@A.foo() - val y = this.foo() - val z = foo() + val x = this@A.foo() + val y = this.foo() + val z = foo() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/thisInInnerClasses.kt b/compiler/testData/diagnostics/tests/thisAndSuper/thisInInnerClasses.kt index 37894b81581..1fa83a6fd52 100644 --- a/compiler/testData/diagnostics/tests/thisAndSuper/thisInInnerClasses.kt +++ b/compiler/testData/diagnostics/tests/thisAndSuper/thisInInnerClasses.kt @@ -3,9 +3,9 @@ class A(val a:Int) { inner class B() { - val x = checkSubtype(this@B) + val x = checkSubtype(this@B) val y = checkSubtype(this@A) - val z = checkSubtype(this) + val z = checkSubtype(this) val Int.xx : Int get() = checkSubtype(this) } } \ 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 ba30eb16fcc..d188752380e 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -2887,6 +2887,111 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { } } + @TestMetadata("compiler/testData/diagnostics/tests/constructorConsistency") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConstructorConsistency extends AbstractDiagnosticsTest { + @TestMetadata("afterInitialization.kt") + public void testAfterInitialization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/afterInitialization.kt"); + doTest(fileName); + } + + @TestMetadata("aliencall.kt") + public void testAliencall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/aliencall.kt"); + doTest(fileName); + } + + public void testAllFilesPresentInConstructorConsistency() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/constructorConsistency"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("assignment.kt") + public void testAssignment() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/assignment.kt"); + doTest(fileName); + } + + @TestMetadata("basic.kt") + public void testBasic() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/basic.kt"); + doTest(fileName); + } + + @TestMetadata("companion.kt") + public void testCompanion() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/companion.kt"); + doTest(fileName); + } + + @TestMetadata("comparison.kt") + public void testComparison() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/comparison.kt"); + doTest(fileName); + } + + @TestMetadata("delegate.kt") + public void testDelegate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/delegate.kt"); + doTest(fileName); + } + + @TestMetadata("derived.kt") + public void testDerived() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/derived.kt"); + doTest(fileName); + } + + @TestMetadata("init.kt") + public void testInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/init.kt"); + doTest(fileName); + } + + @TestMetadata("lambdaInObject.kt") + public void testLambdaInObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/lambdaInObject.kt"); + doTest(fileName); + } + + @TestMetadata("lateInit.kt") + public void testLateInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/lateInit.kt"); + doTest(fileName); + } + + @TestMetadata("localObject.kt") + public void testLocalObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/localObject.kt"); + doTest(fileName); + } + + @TestMetadata("nobacking.kt") + public void testNobacking() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/nobacking.kt"); + doTest(fileName); + } + + @TestMetadata("outer.kt") + public void testOuter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/outer.kt"); + doTest(fileName); + } + + @TestMetadata("property.kt") + public void testProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/property.kt"); + doTest(fileName); + } + + @TestMetadata("propertyAccess.kt") + public void testPropertyAccess() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/propertyAccess.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)