diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 67e60cbd1e8..38a828bd98b 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -515,6 +515,7 @@
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt
index c7ae0a309b8..96d7073c81a 100644
--- a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt
@@ -53,12 +53,15 @@ import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
+import org.jetbrains.kotlin.idea.core.refactoring.getLineStartOffset
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
+import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
import org.jetbrains.kotlin.idea.util.DebuggerUtils
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.application.runReadAction
+import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
@@ -125,12 +128,30 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M
if (lambdaOrFunIfInside != null) {
return SourcePosition.createFromElement(lambdaOrFunIfInside.bodyExpression!!)
}
+ val property = getParameterIfInConstructor(location, psiFile, lineNumber)
+ if (property != null) {
+ return SourcePosition.createFromElement(property)
+ }
return SourcePosition.createFromLine(psiFile, lineNumber)
}
throw NoDataException.INSTANCE
}
+ private fun getParameterIfInConstructor(location: Location, file: KtFile, lineNumber: Int): KtParameter? {
+ val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
+ val elementAt = file.findElementAt(lineStartOffset)
+ val contextElement = KotlinCodeFragmentFactory.getContextElement(elementAt)
+ val methodName = location.method().name()
+ if (contextElement is KtClass && JvmAbi.isGetterName(methodName)) {
+ val parameterForGetter = contextElement.getPrimaryConstructor()?.valueParameters?.firstOrNull() {
+ it.hasValOrVar() && it.name != null && JvmAbi.getterName(it.name!!) == methodName
+ } ?: return null
+ return parameterForGetter
+ }
+ return null
+ }
+
private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? {
val currentLocationFqName = location.declaringType().name()
if (currentLocationFqName == null) return null
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt
index 4097fa9a0be..741f5f16080 100644
--- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt
@@ -180,81 +180,13 @@ private fun getExpressionToAddDebugExpressionBefore(tmpFile: KtFile, contextElem
}
private fun addDebugExpressionBeforeContextElement(codeFragment: KtCodeFragment, contextElement: PsiElement): List {
- val psiFactory = KtPsiFactory(codeFragment)
-
- fun insertNewInitializer(classBody: KtClassBody): PsiElement? {
- val initializer = psiFactory.createAnonymousInitializer()
- val newInitializer = (classBody.addAfter(initializer, classBody.firstChild) as KtAnonymousInitializer)
- val block = newInitializer.body as KtBlockExpression?
- return block?.lastChild
- }
-
- val elementBefore = when {
- contextElement is KtFile -> {
- val fakeFunction = psiFactory.createFunction("fun _debug_fun_() {}")
- contextElement.add(psiFactory.createNewLine())
- val newFakeFun = contextElement.add(fakeFunction) as KtNamedFunction
- newFakeFun.bodyExpression!!.lastChild
- }
- contextElement is KtProperty && !contextElement.isLocal -> {
- val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
- if (delegateExpressionOrInitializer != null) {
- wrapInRunFun(delegateExpressionOrInitializer)
- }
- else {
- val getter = contextElement.getter!!
- if (!getter.hasBlockBody()) {
- wrapInRunFun(getter.bodyExpression!!)
- }
- else {
- (getter.bodyExpression as KtBlockExpression).statements.first()
- }
- }
- }
- contextElement is KtPrimaryConstructor -> {
- val classOrObject = contextElement.getContainingClassOrObject()
- insertNewInitializer(classOrObject.getOrCreateBody())
- }
- contextElement is KtClassOrObject -> {
- insertNewInitializer(contextElement.getOrCreateBody())
- }
- contextElement is KtFunctionLiteral -> {
- val block = contextElement.bodyExpression!!
- block.statements.firstOrNull() ?: block.lastChild
- }
- contextElement is KtDeclarationWithBody && !contextElement.hasBody()-> {
- val block = psiFactory.createBlock("")
- val newBlock = contextElement.add(block) as KtBlockExpression
- newBlock.rBrace
- }
- contextElement is KtDeclarationWithBody && !contextElement.hasBlockBody()-> {
- wrapInRunFun(contextElement.bodyExpression!!)
- }
- contextElement is KtDeclarationWithBody && contextElement.hasBlockBody()-> {
- val block = contextElement.bodyExpression as KtBlockExpression
- val last = block.statements.lastOrNull()
- if (last is KtReturnExpression)
- last
- else
- block.rBrace
- }
- contextElement is KtWhenEntry -> {
- val entryExpression = contextElement.expression
- if (entryExpression is KtBlockExpression) {
- entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
- }
- else {
- wrapInRunFun(entryExpression!!)
- }
- }
- else -> {
- contextElement
- }
- }
+ val elementBefore = findElementBefore(contextElement)
val parent = elementBefore?.parent
if (parent == null || elementBefore == null) return emptyList()
+ val psiFactory = KtPsiFactory(codeFragment)
+
parent.addBefore(psiFactory.createNewLine(), elementBefore)
fun insertExpression(expr: KtElement?): List {
@@ -282,6 +214,84 @@ private fun addDebugExpressionBeforeContextElement(codeFragment: KtCodeFragment,
return insertExpression(debugExpression)
}
+private fun findElementBefore(contextElement: PsiElement): PsiElement? {
+ val psiFactory = KtPsiFactory(contextElement)
+
+ fun insertNewInitializer(classBody: KtClassBody): PsiElement? {
+ val initializer = psiFactory.createAnonymousInitializer()
+ val newInitializer = (classBody.addAfter(initializer, classBody.firstChild) as KtAnonymousInitializer)
+ val block = newInitializer.body as KtBlockExpression?
+ return block?.lastChild
+ }
+
+ return when {
+ contextElement is KtFile -> {
+ val fakeFunction = psiFactory.createFunction("fun _debug_fun_() {}")
+ contextElement.add(psiFactory.createNewLine())
+ val newFakeFun = contextElement.add(fakeFunction) as KtNamedFunction
+ newFakeFun.bodyExpression!!.lastChild
+ }
+ contextElement is KtProperty && !contextElement.isLocal -> {
+ val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
+ if (delegateExpressionOrInitializer != null) {
+ wrapInRunFun(delegateExpressionOrInitializer)
+ }
+ else {
+ val getter = contextElement.getter!!
+ if (!getter.hasBlockBody()) {
+ wrapInRunFun(getter.bodyExpression!!)
+ }
+ else {
+ (getter.bodyExpression as KtBlockExpression).statements.first()
+ }
+ }
+ }
+ contextElement is KtParameter -> {
+ val ownerFunction = contextElement.ownerFunction!!
+ findElementBefore(ownerFunction)
+ }
+ contextElement is KtPrimaryConstructor -> {
+ val classOrObject = contextElement.getContainingClassOrObject()
+ insertNewInitializer(classOrObject.getOrCreateBody())
+ }
+ contextElement is KtClassOrObject -> {
+ insertNewInitializer(contextElement.getOrCreateBody())
+ }
+ contextElement is KtFunctionLiteral -> {
+ val block = contextElement.bodyExpression!!
+ block.statements.firstOrNull() ?: block.lastChild
+ }
+ contextElement is KtDeclarationWithBody && !contextElement.hasBody() -> {
+ val block = psiFactory.createBlock("")
+ val newBlock = contextElement.add(block) as KtBlockExpression
+ newBlock.rBrace
+ }
+ contextElement is KtDeclarationWithBody && !contextElement.hasBlockBody() -> {
+ wrapInRunFun(contextElement.bodyExpression!!)
+ }
+ contextElement is KtDeclarationWithBody && contextElement.hasBlockBody() -> {
+ val block = contextElement.bodyExpression as KtBlockExpression
+ val last = block.statements.lastOrNull()
+ if (last is KtReturnExpression)
+ last
+ else
+ block.rBrace
+ }
+ contextElement is KtWhenEntry -> {
+ val entryExpression = contextElement.expression
+ if (entryExpression is KtBlockExpression) {
+ entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
+ }
+ else {
+ wrapInRunFun(entryExpression!!)
+ }
+ }
+ else -> {
+ contextElement
+ }
+ }
+}
+
private fun replaceByRunFunction(expression: KtExpression): KtCallExpression {
val callExpression = KtPsiFactory(expression).createExpression("run { \n${expression.text} \n}") as KtCallExpression
val replaced = expression.replaced(callExpression)
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSimpleGetterProvider.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSimpleGetterProvider.kt
new file mode 100644
index 00000000000..261bb6cdcbe
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSimpleGetterProvider.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.idea.debugger.stepping
+
+import com.intellij.debugger.engine.SimplePropertyGetterProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.psi.*
+
+class KotlinSimpleGetterProvider : SimplePropertyGetterProvider {
+ override fun isInsideSimpleGetter(element: PsiElement): Boolean {
+ // class A(val a: Int)
+ if (element is KtParameter) {
+ return true
+ }
+
+ val accessor = PsiTreeUtil.getParentOfType(element, KtPropertyAccessor::class.java)
+ if (accessor != null && accessor.isGetter) {
+ val body = accessor.bodyExpression
+ return when (body) {
+ // val a: Int get() { return field }
+ is KtBlockExpression -> {
+ val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false
+ returnedExpression.textMatches("field")
+ }
+ // val a: Int get() = field
+ is KtExpression -> body.textMatches("field")
+ else -> false
+ }
+ }
+
+ val property = PsiTreeUtil.getParentOfType(element, KtProperty::class.java)
+ // val a = foo()
+ if (property != null) {
+ return property.getter == null
+ }
+
+ return false
+ }
+}
diff --git a/idea/testData/debugger/tinyApp/outs/onGetter.out b/idea/testData/debugger/tinyApp/outs/onGetter.out
new file mode 100644
index 00000000000..6b89e8ff169
--- /dev/null
+++ b/idea/testData/debugger/tinyApp/outs/onGetter.out
@@ -0,0 +1,9 @@
+LineBreakpoint created at onGetter.kt:9
+!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! onGetter.OnGetterKt
+Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
+onGetter.kt:9
+onGetter.kt:12
+Compile bytecode for prop
+Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
+
+Process finished with exit code 0
diff --git a/idea/testData/debugger/tinyApp/outs/skipSimpleGetter.out b/idea/testData/debugger/tinyApp/outs/skipSimpleGetter.out
new file mode 100644
index 00000000000..f01d3fec9f3
--- /dev/null
+++ b/idea/testData/debugger/tinyApp/outs/skipSimpleGetter.out
@@ -0,0 +1,8 @@
+LineBreakpoint created at skipSimpleGetter.kt:6
+!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! skipSimpleGetter.SkipSimpleGetterKt
+Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
+skipSimpleGetter.kt:6
+skipSimpleGetter.kt:7
+Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
+
+Process finished with exit code 0
diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onGetter.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onGetter.kt
new file mode 100644
index 00000000000..7c53b8bb642
--- /dev/null
+++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onGetter.kt
@@ -0,0 +1,12 @@
+package onGetter
+
+fun main(args: Array) {
+ val a = A(1)
+ // EXPRESSION: prop
+ // RESULT: 1: I
+ // STEP_INTO: 1
+ //Breakpoint!
+ a.prop
+}
+
+class A(val prop: Int)
\ No newline at end of file
diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepInto/skipSimpleGetter.kt b/idea/testData/debugger/tinyApp/src/stepping/stepInto/skipSimpleGetter.kt
new file mode 100644
index 00000000000..ab6bcf4ca9d
--- /dev/null
+++ b/idea/testData/debugger/tinyApp/src/stepping/stepInto/skipSimpleGetter.kt
@@ -0,0 +1,25 @@
+package skipSimpleGetter
+
+fun main(args: Array) {
+ val a = A(1)
+ //Breakpoint!
+ a.a1 + a.a2 + a.a3 + a.a4
+}
+
+class A(val a4: Int) {
+ // only init
+ val a1 = 1
+
+ // simple get, expression body
+ val a2: Int = 1
+ get() = field
+
+ // simple get, block body
+ val a3: Int = 1
+ get() {
+ return field
+ }
+}
+
+// STEP_INTO: 21
+// SKIP_GETTERS: true
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java
index c518270e866..f0b9b1e7d1c 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java
@@ -241,6 +241,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
doStepIntoTest(fileName);
}
+ @TestMetadata("skipSimpleGetter.kt")
+ public void testSkipSimpleGetter() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/skipSimpleGetter.kt");
+ doStepIntoTest(fileName);
+ }
+
@TestMetadata("stepIntoFromInlineFun.kt")
public void testStepIntoFromInlineFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/stepIntoFromInlineFun.kt");
diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/MockLocation.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/MockLocation.java
index 08ce7265a94..276c3d506ba 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/debugger/MockLocation.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/MockLocation.java
@@ -48,7 +48,7 @@ public class MockLocation implements Location {
@Override
public Method method() {
- throw new UnsupportedOperationException();
+ return new MockMethod();
}
@Override
diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/MockMethod.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/MockMethod.kt
new file mode 100644
index 00000000000..71ed2fcdfda
--- /dev/null
+++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/MockMethod.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.idea.debugger
+
+import com.sun.jdi.Method
+
+class MockMethod : Method {
+ override fun name() = ""
+
+ override fun isSynthetic() = throw UnsupportedOperationException()
+ override fun isFinal() = throw UnsupportedOperationException()
+ override fun isStatic() = throw UnsupportedOperationException()
+ override fun declaringType() = throw UnsupportedOperationException()
+ override fun signature() = throw UnsupportedOperationException()
+ override fun genericSignature() = throw UnsupportedOperationException()
+ override fun variables() = throw UnsupportedOperationException()
+ override fun variablesByName(name: String?) = throw UnsupportedOperationException()
+ override fun bytecodes() = throw UnsupportedOperationException()
+ override fun isBridge() = throw UnsupportedOperationException()
+ override fun isObsolete() = throw UnsupportedOperationException()
+ override fun isSynchronized() = throw UnsupportedOperationException()
+ override fun allLineLocations() = throw UnsupportedOperationException()
+ override fun allLineLocations(stratum: String?, sourceName: String?) = throw UnsupportedOperationException()
+ override fun isNative() = throw UnsupportedOperationException()
+ override fun locationOfCodeIndex(codeIndex: Long) = throw UnsupportedOperationException()
+ override fun arguments() = throw UnsupportedOperationException()
+ override fun isAbstract() = throw UnsupportedOperationException()
+ override fun isVarArgs() = throw UnsupportedOperationException()
+ override fun returnTypeName() = throw UnsupportedOperationException()
+ override fun argumentTypes( ) = throw UnsupportedOperationException()
+ override fun isConstructor() = throw UnsupportedOperationException()
+ override fun locationsOfLine(lineNumber: Int) = throw UnsupportedOperationException()
+ override fun locationsOfLine(stratum: String?, sourceName: String?, lineNumber: Int) = throw UnsupportedOperationException()
+ override fun argumentTypeNames() = throw UnsupportedOperationException()
+ override fun returnType() = throw UnsupportedOperationException()
+ override fun isStaticInitializer() = throw UnsupportedOperationException()
+ override fun location() = throw UnsupportedOperationException()
+ override fun compareTo(other: Method?) = throw UnsupportedOperationException()
+ override fun isPackagePrivate() = throw UnsupportedOperationException()
+ override fun isPrivate() = throw UnsupportedOperationException()
+ override fun isProtected() = throw UnsupportedOperationException()
+ override fun isPublic() = throw UnsupportedOperationException()
+ override fun modifiers() = throw UnsupportedOperationException()
+ override fun virtualMachine() = throw UnsupportedOperationException()
+}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java
index 81a59eb6e1c..d8c4d5ee9e1 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java
@@ -223,6 +223,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doSingleBreakpointTest(fileName);
}
+ @TestMetadata("onGetter.kt")
+ public void testOnGetter() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onGetter.kt");
+ doSingleBreakpointTest(fileName);
+ }
+
@TestMetadata("onObjectHeader.kt")
public void testOnObjectHeader() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onObjectHeader.kt");