diff --git a/idea/resources/inspectionDescriptions/MayBeConstant.html b/idea/resources/inspectionDescriptions/MayBeConstant.html
new file mode 100644
index 00000000000..778dbd47206
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/MayBeConstant.html
@@ -0,0 +1,6 @@
+
+
+This inspection reports object and top-level val that might be declared as const
+for better performance and Java interoperability.
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index ecf8fb1c045..d1d23c2cc8e 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -2712,6 +2712,15 @@
cleanupTool="true"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/MayBeConstantInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/MayBeConstantInspection.kt
new file mode 100644
index 00000000000..21d8dc609bb
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/MayBeConstantInspection.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010-2017 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.idea.inspections
+
+import com.intellij.codeInspection.IntentionWrapper
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.PsiElementVisitor
+import org.jetbrains.kotlin.descriptors.VariableDescriptor
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.idea.quickfix.AddConstModifierFix
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.KtVisitorVoid
+import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
+import org.jetbrains.kotlin.resolve.constants.evaluate.isStandaloneOnlyConstant
+import org.jetbrains.kotlin.resolve.jvm.annotations.hasJvmFieldAnnotation
+import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
+
+class MayBeConstantInspection : AbstractKotlinInspection() {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitProperty(property: KtProperty) {
+ super.visitProperty(property)
+
+ if (property.isLocal || property.isVar) return
+ val initializer = property.initializer ?: return
+ if (property.hasModifier(KtTokens.CONST_KEYWORD)) return
+ // Top-level or object only
+ if (property.containingClassOrObject is KtClass) return
+
+ // For some reason constant evaluation does not work for property.analyze()
+ val context = initializer.analyze(BodyResolveMode.PARTIAL)
+ val propertyDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, property] as? VariableDescriptor ?: return
+ val withJvmField = propertyDescriptor.hasJvmFieldAnnotation()
+ if (property.annotationEntries.isNotEmpty() && !withJvmField) return
+
+ val initializerValue = ConstantExpressionEvaluator.getConstant(
+ initializer, context
+ )?.toConstantValue(propertyDescriptor.type) ?: return
+ if (initializerValue.isStandaloneOnlyConstant()) return
+
+ holder.registerProblem(
+ property.nameIdentifier ?: property,
+ if (withJvmField) "'const' might be used instead of '@JvmField'" else "Might be 'const'",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ IntentionWrapper(AddConstModifierFix(property), property.containingFile)
+ )
+ }
+ }
+ }
+}
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/.inspection b/idea/testData/inspectionsLocal/mayBeConstant/.inspection
new file mode 100644
index 00000000000..8f8801c81fe
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.MayBeConstantInspection
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt b/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt
new file mode 100644
index 00000000000..8907a84cf6a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt
@@ -0,0 +1,9 @@
+// WITH_RUNTIME
+
+const val y = 0
+
+class Your {
+ companion object {
+ @JvmField val z = y + 3
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt.after
new file mode 100644
index 00000000000..d2173f10342
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt.after
@@ -0,0 +1,9 @@
+// WITH_RUNTIME
+
+const val y = 0
+
+class Your {
+ companion object {
+ const val z = y + 3
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/companion.kt b/idea/testData/inspectionsLocal/mayBeConstant/companion.kt
new file mode 100644
index 00000000000..1c8a9fe5651
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/companion.kt
@@ -0,0 +1,5 @@
+class Your {
+ companion object {
+ val IMPORTANT = 2 * 2
+ }
+}
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/companion.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/companion.kt.after
new file mode 100644
index 00000000000..99c046c9420
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/companion.kt.after
@@ -0,0 +1,5 @@
+class Your {
+ companion object {
+ const val IMPORTANT = 2 * 2
+ }
+}
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/const.kt b/idea/testData/inspectionsLocal/mayBeConstant/const.kt
new file mode 100644
index 00000000000..04f1e643684
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/const.kt
@@ -0,0 +1,4 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+const val x = 13
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/getter.kt b/idea/testData/inspectionsLocal/mayBeConstant/getter.kt
new file mode 100644
index 00000000000..20d05f373d3
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/getter.kt
@@ -0,0 +1,3 @@
+// PROBLEM: none
+
+val withGetter get() = 42
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/inClass.kt b/idea/testData/inspectionsLocal/mayBeConstant/inClass.kt
new file mode 100644
index 00000000000..8dd01068792
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/inClass.kt
@@ -0,0 +1,6 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+class My {
+ @JvmField val x = 13
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/nonJvmFieldAnnotated.kt b/idea/testData/inspectionsLocal/mayBeConstant/nonJvmFieldAnnotated.kt
new file mode 100644
index 00000000000..3719b74091f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/nonJvmFieldAnnotated.kt
@@ -0,0 +1,6 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+annotation class A
+
+@A val x = 55
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/object.kt b/idea/testData/inspectionsLocal/mayBeConstant/object.kt
new file mode 100644
index 00000000000..e002478cdd1
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/object.kt
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+object O {
+ @JvmField val s = "He" + "llo"
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/object.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/object.kt.after
new file mode 100644
index 00000000000..9d28f11dce0
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/object.kt.after
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+object O {
+ const val s = "He" + "llo"
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/simple.kt b/idea/testData/inspectionsLocal/mayBeConstant/simple.kt
new file mode 100644
index 00000000000..e883f9c3f4f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/simple.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+@JvmField val x = 1
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/simple.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/simple.kt.after
new file mode 100644
index 00000000000..81fccdefc30
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/simple.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+const val x = 1
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt b/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt
new file mode 100644
index 00000000000..4d26dd472d8
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt
@@ -0,0 +1 @@
+val x = 1
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt.after
new file mode 100644
index 00000000000..26323def675
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt.after
@@ -0,0 +1 @@
+const val x = 1
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/mayBeConstant/var.kt b/idea/testData/inspectionsLocal/mayBeConstant/var.kt
new file mode 100644
index 00000000000..237f96b7f42
--- /dev/null
+++ b/idea/testData/inspectionsLocal/mayBeConstant/var.kt
@@ -0,0 +1,3 @@
+// PROBLEM: none
+
+var n = 1
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
index 9d9831d1912..6023917c8ea 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -1281,6 +1281,75 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
+ @TestMetadata("idea/testData/inspectionsLocal/mayBeConstant")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class MayBeConstant extends AbstractLocalInspectionTest {
+ public void testAllFilesPresentInMayBeConstant() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/mayBeConstant"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("cascadeConst.kt")
+ public void testCascadeConst() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("companion.kt")
+ public void testCompanion() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/companion.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("const.kt")
+ public void testConst() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/const.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("getter.kt")
+ public void testGetter() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/getter.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("inClass.kt")
+ public void testInClass() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/inClass.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("nonJvmFieldAnnotated.kt")
+ public void testNonJvmFieldAnnotated() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/nonJvmFieldAnnotated.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("object.kt")
+ public void testObject() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/object.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/simple.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("simplest.kt")
+ public void testSimplest() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/simplest.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("var.kt")
+ public void testVar() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/var.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/inspectionsLocal/memberVisibilityCanBePrivate")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)