From e3967b9fa0d4ce815533bd9bd174e6eaf2f84885 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 3 Sep 2015 01:32:58 +0300 Subject: [PATCH] Diagnostic on lateinit --- .../jetbrains/kotlin/diagnostics/Errors.java | 1 + .../rendering/DefaultErrorMessages.java | 1 + .../kotlin/resolve/DeclarationsChecker.java | 55 +++++++++++++++-- .../testData/diagnostics/tests/LateInit.kt | 57 +++++++++++++++++ .../testData/diagnostics/tests/LateInit.txt | 61 +++++++++++++++++++ .../checkers/JetDiagnosticsTestGenerated.java | 6 ++ 6 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/LateInit.kt create mode 100644 compiler/testData/diagnostics/tests/LateInit.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index c8cab080019..3b3af024656 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -315,6 +315,7 @@ public interface Errors { DiagnosticFactory0 PROPERTY_INITIALIZER_NO_BACKING_FIELD = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 PROPERTY_INITIALIZER_IN_TRAIT = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 FINAL_PROPERTY_IN_TRAIT = DiagnosticFactory0.create(ERROR, FINAL_MODIFIER); + DiagnosticFactory0 INAPPLICABLE_LATEINIT_MODIFIER = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 BACKING_FIELD_IN_TRAIT = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE); DiagnosticFactory2 ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS = DiagnosticFactory2.create(ERROR, ABSTRACT_MODIFIER); 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 bf205164418..3bb9a1acb82 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -199,6 +199,7 @@ public class DefaultErrorMessages { MAP.put(LOCAL_VARIABLE_WITH_DELEGATE, "Local variables are not allowed to have delegates"); MAP.put(GETTER_VISIBILITY_DIFFERS_FROM_PROPERTY_VISIBILITY, "Getter visibility must be the same as property visibility"); + MAP.put(INAPPLICABLE_LATEINIT_MODIFIER, "''lateinit'' modifier is allowed only on non-null member properties with a backing field"); MAP.put(BACKING_FIELD_IN_TRAIT, "Property in an interface cannot have a backing field"); MAP.put(MUST_BE_INITIALIZED, "Property must be initialized"); MAP.put(MUST_BE_INITIALIZED_OR_BE_ABSTRACT, "Property must be initialized or be abstract"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.java index 36c1577d474..43c6d1ba4c7 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.java @@ -22,15 +22,13 @@ import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; 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.Errors; import org.jetbrains.kotlin.lexer.JetModifierKeywordToken; import org.jetbrains.kotlin.lexer.JetTokens; import org.jetbrains.kotlin.psi.*; -import org.jetbrains.kotlin.types.JetType; -import org.jetbrains.kotlin.types.SubstitutionUtils; -import org.jetbrains.kotlin.types.TypeConstructor; -import org.jetbrains.kotlin.types.TypeProjection; +import org.jetbrains.kotlin.types.*; import org.jetbrains.kotlin.types.checker.JetTypeChecker; import java.util.Collection; @@ -260,6 +258,7 @@ public class DeclarationsChecker { PropertyDescriptor propertyDescriptor = trace.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter); if (propertyDescriptor != null) { modifiersChecker.checkModifiersForDeclaration(parameter, propertyDescriptor); + checkPropertyLateInit(parameter, propertyDescriptor); } } @@ -325,10 +324,56 @@ public class DeclarationsChecker { if (containingDeclaration instanceof ClassDescriptor) { checkPropertyAbstractness(property, propertyDescriptor, (ClassDescriptor) containingDeclaration); } + checkPropertyLateInit(property, propertyDescriptor); checkPropertyInitializer(property, propertyDescriptor); checkAccessors(property, propertyDescriptor); } + private void checkPropertyLateInit(@NotNull JetCallableDeclaration property, @NotNull PropertyDescriptor propertyDescriptor) { + JetModifierList modifierList = property.getModifierList(); + if (modifierList == null) return; + PsiElement modifier = modifierList.getModifier(JetTokens.LATE_INIT_KEYWORD); + if (modifier == null) return; + + boolean hasBackingField = + Boolean.TRUE.equals(trace.getBindingContext().get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor)); + + boolean hasDelegateOrInitializer = false; + boolean hasCorrespondingValueParameter = false; + + if (property instanceof JetProperty) { + hasDelegateOrInitializer = ((JetProperty) property).hasDelegateExpressionOrInitializer(); + } + else if (property instanceof JetParameter) { + hasCorrespondingValueParameter = true; + } + + PropertyGetterDescriptor getter = propertyDescriptor.getGetter(); + PropertySetterDescriptor setter = propertyDescriptor.getSetter(); + + boolean customGetterOrSetter = false; + if (getter != null) { + customGetterOrSetter = getter.hasBody(); + } + if (setter != null) { + customGetterOrSetter |= setter.hasBody(); + } + + boolean returnTypeIsNullable = true; + boolean returnTypeIsPrimitive = true; + + JetType returnType = propertyDescriptor.getReturnType(); + if (returnType != null) { + returnTypeIsNullable = TypeUtils.isNullableType(returnType); + returnTypeIsPrimitive = KotlinBuiltIns.isPrimitiveType(returnType); + } + + if (!hasBackingField || hasCorrespondingValueParameter || hasDelegateOrInitializer || customGetterOrSetter + || returnTypeIsNullable || returnTypeIsPrimitive || propertyDescriptor.getExtensionReceiverParameter() != null) { + trace.report(INAPPLICABLE_LATEINIT_MODIFIER.on(modifier)); + } + } + private void checkPropertyAbstractness( @NotNull JetProperty property, @NotNull PropertyDescriptor propertyDescriptor, @@ -392,7 +437,7 @@ public class DeclarationsChecker { if (initializer == null && delegate == null) { boolean error = false; - if (backingFieldRequired && !inTrait && + if (backingFieldRequired && !inTrait && !propertyDescriptor.isLateInit() && Boolean.TRUE.equals(trace.getBindingContext().get(BindingContext.IS_UNINITIALIZED, propertyDescriptor))) { if (!(containingDeclaration instanceof ClassDescriptor) || hasAccessorImplementation) { error = true; diff --git a/compiler/testData/diagnostics/tests/LateInit.kt b/compiler/testData/diagnostics/tests/LateInit.kt new file mode 100644 index 00000000000..158af3e8a0c --- /dev/null +++ b/compiler/testData/diagnostics/tests/LateInit.kt @@ -0,0 +1,57 @@ +class CustomDelegate { + public fun get(thisRef: Any?, prop: PropertyMetadata): String = prop.name +} + +public abstract class A(lateinit val p2: String) { + + public lateinit val a: String + lateinit val b: T + private lateinit var c: CharSequence + + lateinit val d: String + get + + public lateinit var e: String + get + private set + + fun a() { + lateinit val a: String + } + + lateinit val e1: V + lateinit val e2: String? + lateinit val e3: Int + lateinit val e4: Int? + lateinit var e5 = "A" + lateinit var e6 = 3 + + lateinit val e7 by CustomDelegate() + + lateinit val e8: String + get() = "A" + + lateinit var e9: String + set(v) { $e9 = v } + + abstract lateinit val e10: String + + lateinit val String.e11: String + + lateinit var String.e12: String +} + +lateinit val topLevel: String +lateinit var topLevelMutable: String + +public interface Intf { + lateinit val str: String +} + +public abstract class AbstractClass { + abstract val str: String +} + +public class AbstractClassImpl : AbstractClass() { + override lateinit val str: String +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/LateInit.txt b/compiler/testData/diagnostics/tests/LateInit.txt new file mode 100644 index 00000000000..84c23d0597a --- /dev/null +++ b/compiler/testData/diagnostics/tests/LateInit.txt @@ -0,0 +1,61 @@ +package + +internal lateinit val topLevel: kotlin.String +internal lateinit var topLevelMutable: kotlin.String + +public abstract class A { + public constructor A(/*0*/ p2: kotlin.String) + public final lateinit val a: kotlin.String + internal final lateinit val b: T + private final lateinit var c: kotlin.CharSequence + internal final lateinit val d: kotlin.String + public final lateinit var e: kotlin.String + internal final lateinit val e1: V + internal abstract lateinit val e10: kotlin.String + internal final lateinit val e2: kotlin.String? + internal final lateinit val e3: kotlin.Int + internal final lateinit val e4: kotlin.Int? + internal final lateinit var e5: kotlin.String + internal final lateinit var e6: kotlin.Int + internal final lateinit val e7: kotlin.String + internal final lateinit val e8: kotlin.String + internal final lateinit var e9: kotlin.String + internal final val p2: kotlin.String + internal final lateinit val kotlin.String.e11: kotlin.String + internal final lateinit var kotlin.String.e12: kotlin.String + internal final fun a(): kotlin.Unit + 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 abstract class AbstractClass { + public constructor AbstractClass() + internal abstract val str: 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 +} + +public final class AbstractClassImpl : AbstractClass { + public constructor AbstractClassImpl() + internal open override /*1*/ lateinit val str: 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 +} + +internal final class CustomDelegate { + public constructor CustomDelegate() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun get(/*0*/ thisRef: kotlin.Any?, /*1*/ prop: kotlin.PropertyMetadata): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface Intf { + internal abstract lateinit val str: 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/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 33c81548583..e8384820870 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -301,6 +301,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("LateInit.kt") + public void testLateInit() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/LateInit.kt"); + doTest(fileName); + } + @TestMetadata("LocalClassAndShortSubpackageNames.kt") public void testLocalClassAndShortSubpackageNames() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/LocalClassAndShortSubpackageNames.kt");