Introduce Variable: Add type argument to initializer if they can't be inferred from the context

#KT-5466 Fixed
This commit is contained in:
Alexey Sedunov
2015-12-16 16:28:14 +03:00
parent ea8da18338
commit cfe1c44260
17 changed files with 181 additions and 46 deletions
@@ -41,7 +41,9 @@ import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedShortening import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedShortening
import org.jetbrains.kotlin.idea.core.refactoring.addTypeArgumentsIfNeeded
import org.jetbrains.kotlin.idea.core.refactoring.checkConflictsInteractively import org.jetbrains.kotlin.idea.core.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.core.refactoring.getQualifiedTypeArgumentList
import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.refactoring.move.PackageNameInfo import org.jetbrains.kotlin.idea.refactoring.move.PackageNameInfo
import org.jetbrains.kotlin.idea.refactoring.move.lazilyProcessInternalReferencesToUpdateOnPackageNameChange import org.jetbrains.kotlin.idea.refactoring.move.lazilyProcessInternalReferencesToUpdateOnPackageNameChange
@@ -55,8 +57,6 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCallWithAssert
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.types.expressions.OperatorConventions
@@ -156,7 +156,7 @@ class KotlinInlineValHandler : InlineActionHandler() {
?: return reportAmbiguousAssignment(project, editor, name, assignments) ?: return reportAmbiguousAssignment(project, editor, name, assignments)
} }
val typeArgumentsForCall = getTypeArgumentsStringForCall(initializer) val typeArgumentsForCall = getQualifiedTypeArgumentList(initializer)
val parametersForFunctionLiteral = getParametersForFunctionLiteral(initializer) val parametersForFunctionLiteral = getParametersForFunctionLiteral(initializer)
val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file } val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file }
@@ -217,7 +217,9 @@ class KotlinInlineValHandler : InlineActionHandler() {
declaration.delete() declaration.delete()
if (inlinedExpressions.isNotEmpty()) { if (inlinedExpressions.isNotEmpty()) {
typeArgumentsForCall?.let { addTypeArguments(it, inlinedExpressions) } if (typeArgumentsForCall != null) {
inlinedExpressions.forEach { addTypeArgumentsIfNeeded(it, typeArgumentsForCall) }
}
parametersForFunctionLiteral?.let { addFunctionLiteralParameterTypes(it, inlinedExpressions) } parametersForFunctionLiteral?.let { addFunctionLiteralParameterTypes(it, inlinedExpressions) }
@@ -348,35 +350,4 @@ class KotlinInlineValHandler : InlineActionHandler() {
hasCantInferParameter || hasUnresolvedItOrThis hasCantInferParameter || hasUnresolvedItOrThis
} }
} }
private fun addTypeArguments(typeArguments: String, inlinedExpressions: List<KtExpression>) {
val containingFile = inlinedExpressions.first().getContainingKtFile()
val callsToAddArguments = inlinedExpressions.mapNotNull {
val context = it.analyze(BodyResolveMode.PARTIAL)
val call = it.getCallWithAssert(context)
val callElement = call.callElement
if (callElement is KtCallExpression &&
hasIncompleteTypeInferenceDiagnostic(call, context) &&
call.typeArgumentList == null) callElement else null
}
val psiFactory = KtPsiFactory(containingFile)
for (call in callsToAddArguments) {
call.addAfter(psiFactory.createTypeArguments("<$typeArguments>"), call.calleeExpression)
ShortenReferences.DEFAULT.process(call.typeArgumentList!!)
}
}
private fun getTypeArgumentsStringForCall(initializer: KtExpression): String? {
val context = initializer.analyze(BodyResolveMode.PARTIAL)
val call = initializer.getResolvedCall(context) ?: return null
val typeArgumentMap = call.typeArguments
val typeArguments = call.candidateDescriptor.typeParameters.mapNotNull { typeArgumentMap[it] }
return typeArguments.joinToString { IdeDescriptorRenderers.SOURCE_CODE_FOR_TYPE_ARGUMENTS.renderType(it) }
}
private fun hasIncompleteTypeInferenceDiagnostic(call: Call, context: BindingContext): Boolean {
val callee = call.calleeExpression ?: return false
return context.diagnostics.forElement(callee).any { it.factory == Errors.TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER }
}
} }
@@ -42,9 +42,7 @@ import org.jetbrains.kotlin.idea.analysis.computeTypeInfoInContext
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.* import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.core.refactoring.Pass import org.jetbrains.kotlin.idea.core.refactoring.*
import org.jetbrains.kotlin.idea.core.refactoring.chooseContainerElementIfNecessary
import org.jetbrains.kotlin.idea.core.refactoring.removeTemplateEntryBracesIfPossible
import org.jetbrains.kotlin.idea.intentions.ConvertToBlockBodyIntention import org.jetbrains.kotlin.idea.intentions.ConvertToBlockBodyIntention
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil
@@ -411,7 +409,9 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
project: Project, project: Project,
editor: Editor, editor: Editor,
declaration: KtDestructuringDeclaration, declaration: KtDestructuringDeclaration,
suggestedNames: List<Collection<String>>) { suggestedNames: List<Collection<String>>,
postProcess: (KtDeclaration) -> Unit
) {
StartMarkAction.canStart(project)?.let { return } StartMarkAction.canStart(project)?.let { return }
val builder = TemplateBuilderImpl(declaration) val builder = TemplateBuilderImpl(declaration)
@@ -441,6 +441,9 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
} }
override fun templateFinished(template: Template?, brokenOff: Boolean) { override fun templateFinished(template: Template?, brokenOff: Boolean) {
if (!brokenOff) {
postProcess(declaration)
}
finishMarkAction() finishMarkAction()
} }
@@ -518,12 +521,24 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
return showErrorHint(project, editor, KotlinRefactoringBundle.message("cannot.refactor.expression.has.unit.type")) return showErrorHint(project, editor, KotlinRefactoringBundle.message("cannot.refactor.expression.has.unit.type"))
} }
val typeArgumentList = getQualifiedTypeArgumentList(KtPsiUtil.safeDeparenthesize(physicalExpression))
val isInplaceAvailable = editor != null val isInplaceAvailable = editor != null
&& editor.settings.isVariableInplaceRenameEnabled && editor.settings.isVariableInplaceRenameEnabled
&& !ApplicationManager.getApplication().isUnitTestMode && !ApplicationManager.getApplication().isUnitTestMode
val allOccurrences = occurrencesToReplace ?: expression.findOccurrences(occurrenceContainer) val allOccurrences = occurrencesToReplace ?: expression.findOccurrences(occurrenceContainer)
fun postProcess(declaration: KtDeclaration) {
if (typeArgumentList == null) return
val initializer = when (declaration) {
is KtProperty -> declaration.initializer
is KtDestructuringDeclaration -> declaration.initializer
else -> null
} ?: return
runWriteAction { addTypeArgumentsIfNeeded(initializer, typeArgumentList) }
}
val callback = Pass<OccurrencesChooser.ReplaceChoice> { replaceChoice -> val callback = Pass<OccurrencesChooser.ReplaceChoice> { replaceChoice ->
val allReplaces = when (replaceChoice) { val allReplaces = when (replaceChoice) {
OccurrencesChooser.ReplaceChoice.ALL -> allOccurrences OccurrencesChooser.ReplaceChoice.ALL -> allOccurrences
@@ -545,6 +560,7 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
calculateAnchor(commonParent, commonContainer, allReplaces), calculateAnchor(commonParent, commonContainer, allReplaces),
NewDeclarationNameValidator.Target.VARIABLES NewDeclarationNameValidator.Target.VARIABLES
) )
val suggestedNames = if (componentFunctions.isNotEmpty()) { val suggestedNames = if (componentFunctions.isNotEmpty()) {
val collectingValidator = CollectingNameValidator(filter = validator) val collectingValidator = CollectingNameValidator(filter = validator)
componentFunctions.map { suggestNamesForComponent(it, project, collectingValidator) } componentFunctions.map { suggestNamesForComponent(it, project, collectingValidator) }
@@ -556,10 +572,12 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
validator, validator,
"value").singletonList() "value").singletonList()
} }
val introduceVariableContext = IntroduceVariableContext( val introduceVariableContext = IntroduceVariableContext(
expression, suggestedNames, allReplaces, commonContainer, commonParent, expression, suggestedNames, allReplaces, commonContainer, commonParent,
replaceOccurrence, noTypeInference, expressionType, componentFunctions, bindingContext, resolutionFacade replaceOccurrence, noTypeInference, expressionType, componentFunctions, bindingContext, resolutionFacade
) )
project.executeCommand(INTRODUCE_VARIABLE, null) { project.executeCommand(INTRODUCE_VARIABLE, null) {
runWriteAction { introduceVariableContext.runRefactoring() } runWriteAction { introduceVariableContext.runRefactoring() }
@@ -573,7 +591,10 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
editor.caretModel.moveToOffset(property.textOffset) editor.caretModel.moveToOffset(property.textOffset)
editor.selectionModel.removeSelection() editor.selectionModel.removeSelection()
if (!isInplaceAvailable) return@executeCommand if (!isInplaceAvailable) {
postProcess(property)
return@executeCommand
}
PsiDocumentManager.getInstance(project).commitDocument(editor.document) PsiDocumentManager.getInstance(project).commitDocument(editor.document)
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document)
@@ -590,12 +611,13 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
expressionType, expressionType,
noTypeInference, noTypeInference,
project, project,
editor editor,
::postProcess
).startInplaceIntroduceTemplate() ).startInplaceIntroduceTemplate()
} }
is KtDestructuringDeclaration -> { is KtDestructuringDeclaration -> {
executeMultiDeclarationTemplate(project, editor, property, suggestedNames) executeMultiDeclarationTemplate(project, editor, property, suggestedNames, ::postProcess)
} }
else -> throw AssertionError("Unexpected declaration: ${property.getElementTextWithContext()}") else -> throw AssertionError("Unexpected declaration: ${property.getElementTextWithContext()}")
@@ -33,9 +33,7 @@ import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractKotlinInplaceIntroducer import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractKotlinInplaceIntroducer
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import java.awt.BorderLayout import java.awt.BorderLayout
@@ -50,7 +48,8 @@ public class KotlinVariableInplaceIntroducer(
val expressionType: KotlinType?, val expressionType: KotlinType?,
val noTypeInference: Boolean, val noTypeInference: Boolean,
project: Project, project: Project,
editor: Editor editor: Editor,
private val postProcess: (KtDeclaration) -> Unit
): AbstractKotlinInplaceIntroducer<KtProperty>( ): AbstractKotlinInplaceIntroducer<KtProperty>(
addedVariable, addedVariable,
originalExpression, originalExpression,
@@ -155,5 +154,6 @@ public class KotlinVariableInplaceIntroducer(
it.replace(replacement) it.replace(replacement)
} }
} }
postProcess(addedVariable)
} }
} }
@@ -63,6 +63,7 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMemberDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getJavaMemberDescriptor
@@ -71,7 +72,9 @@ import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.intentions.RemoveCurlyBracesFromTemplateIntention import org.jetbrains.kotlin.idea.intentions.RemoveCurlyBracesFromTemplateIntention
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.idea.util.string.collapseSpaces import org.jetbrains.kotlin.idea.util.string.collapseSpaces
import org.jetbrains.kotlin.j2k.ConverterSettings import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
@@ -83,6 +86,9 @@ import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCallWithAssert
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import java.io.File import java.io.File
import java.lang.annotation.Retention import java.lang.annotation.Retention
import java.util.* import java.util.*
@@ -769,4 +775,27 @@ fun dropOverrideKeywordIfNecessary(element: KtNamedDeclaration) {
if (callableDescriptor.overriddenDescriptors.isEmpty()) { if (callableDescriptor.overriddenDescriptors.isEmpty()) {
element.removeModifier(KtTokens.OVERRIDE_KEYWORD) element.removeModifier(KtTokens.OVERRIDE_KEYWORD)
} }
}
fun getQualifiedTypeArgumentList(initializer: KtExpression): KtTypeArgumentList? {
val context = initializer.analyze(BodyResolveMode.PARTIAL)
val call = initializer.getResolvedCall(context) ?: return null
val typeArgumentMap = call.typeArguments
val typeArguments = call.candidateDescriptor.typeParameters.mapNotNull { typeArgumentMap[it] }
val renderedList = typeArguments.joinToString(prefix = "<", postfix = ">") {
IdeDescriptorRenderers.SOURCE_CODE_FOR_TYPE_ARGUMENTS.renderType(it)
}
return KtPsiFactory(initializer).createTypeArguments(renderedList)
}
fun addTypeArgumentsIfNeeded(expression: KtExpression, typeArgumentList: KtTypeArgumentList) {
val context = expression.analyze(BodyResolveMode.PARTIAL)
val call = expression.getCallWithAssert(context)
val callElement = call.callElement as? KtCallExpression ?: return
if (call.typeArgumentList != null) return
val callee = call.calleeExpression ?: return
if (context.diagnostics.forElement(callee).all { it.factory != Errors.TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER }) return
callElement.addAfter(typeArgumentList, callElement.calleeExpression)
ShortenReferences.DEFAULT.process(callElement.typeArgumentList!!)
} }
@@ -0,0 +1,6 @@
// WITH_RUNTIME
import java.util.ArrayList
fun f() {
val list: List<List<List<Int>>> = ArrayList(<selection>ArrayList(listOf())</selection>)
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
import java.util.ArrayList
fun f() {
val p0 = ArrayList<List<List<Int>>>(listOf())
val list: List<List<List<Int>>> = ArrayList(p0)
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
import java.util.ArrayList
fun f() {
val v : List<List<Int>> = ArrayList(<selection>listOf()</selection>)
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
import java.util.ArrayList
fun f() {
val p0 = listOf<List<Int>>()
val v : List<List<Int>> = ArrayList(p0)
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun f() {
val v : List<Int> = <selection>(listOf())</selection>
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun f() {
val listOf = listOf<Int>()
val v : List<Int> = listOf
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun <T> String.ext(): List<T> = listOf()
fun f() {
val v : List<Int> = <selection>"".ext()</selection>
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun <T> String.ext(): List<T> = listOf()
fun f() {
val ext = "".ext<Int>()
val v : List<Int> = ext
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun f() {
val v : List<Int> = <selection>listOf()</selection>
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun f() {
val listOf = listOf<Int>()
val v : List<Int> = listOf
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun f() {
val v : List<Int> = <selection>listOf()</selection>
val u : List<String> = listOf()
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun f() {
val listOf = listOf<Int>()
val v : List<Int> = listOf
val u : List<String> = listOf()
}
@@ -457,6 +457,51 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
doIntroduceVariableTest(fileName); doIntroduceVariableTest(fileName);
} }
@TestMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ExplicateTypeArguments extends AbstractExtractionTest {
public void testAllFilesPresentInExplicateTypeArguments() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceVariable/explicateTypeArguments"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
}
@TestMetadata("DeeperNestedCall.kt")
public void testDeeperNestedCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/DeeperNestedCall.kt");
doIntroduceVariableTest(fileName);
}
@TestMetadata("NestedCall.kt")
public void testNestedCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/NestedCall.kt");
doIntroduceVariableTest(fileName);
}
@TestMetadata("Parenthesized.kt")
public void testParenthesized() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/Parenthesized.kt");
doIntroduceVariableTest(fileName);
}
@TestMetadata("Qualified.kt")
public void testQualified() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/Qualified.kt");
doIntroduceVariableTest(fileName);
}
@TestMetadata("Simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/Simple.kt");
doIntroduceVariableTest(fileName);
}
@TestMetadata("UnmatchedOccurrences.kt")
public void testUnmatchedOccurrences() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/UnmatchedOccurrences.kt");
doIntroduceVariableTest(fileName);
}
}
@TestMetadata("idea/testData/refactoring/introduceVariable/extractToScope") @TestMetadata("idea/testData/refactoring/introduceVariable/extractToScope")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)