From 4844df2c6ac79acbf21f68cacd06c85338989e5e Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Mon, 8 Apr 2019 19:06:11 +0300 Subject: [PATCH] New J2K: calculate `for` loop variable nullability --- .../kotlin/nj2k/NewJ2kPostProcessings.kt | 37 ++++++++++++++++- .../constraintsCollector.kt | 41 +++++++++++++++++-- .../kotlin/nj2k/ConversionsRunner.kt | 3 -- .../nj2k/conversions/ForInConversion.kt | 29 ------------- nj2k/testData/nullabilityAnalysis/loops.kt | 16 ++++++++ .../nullabilityAnalysis/loops.kt.after | 16 ++++++++ .../NullabilityAnalysisTestGenerated.java | 5 +++ 7 files changed, 110 insertions(+), 37 deletions(-) delete mode 100644 nj2k/src/org/jetbrains/kotlin/nj2k/conversions/ForInConversion.kt create mode 100644 nj2k/testData/nullabilityAnalysis/loops.kt create mode 100644 nj2k/testData/nullabilityAnalysis/loops.kt.after diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/NewJ2kPostProcessings.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/NewJ2kPostProcessings.kt index 39b8df9c81a..266814aaf69 100644 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/NewJ2kPostProcessings.kt +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/NewJ2kPostProcessings.kt @@ -78,6 +78,29 @@ data class OneTimeProcessingGroup(val processings: List) : Processin constructor(vararg processings: Processing) : this(processings.toList()) } +private abstract class CheckableProcessing(private val classTag: KClass) : NewJ2kPostProcessing { + protected open fun check(element: E): Boolean = + check(element, null) + + protected open fun check(element: E, settings: ConverterSettings?): Boolean = + check(element) + + protected abstract fun action(element: E) + + override fun createAction(element: PsiElement, diagnostics: Diagnostics, settings: ConverterSettings?): (() -> Unit)? { + if (!element::class.isSubclassOf(classTag)) return null + @Suppress("UNCHECKED_CAST") + if (!check(element as E, settings)) return null + return { + if (element.isValid && check(element, settings)) { + action(element) + } + } + } + + override val writeActionNeeded: Boolean = true +} + object NewJ2KPostProcessingRegistrar { private fun Processing.processings(): Sequence = @@ -308,7 +331,8 @@ object NewJ2KPostProcessingRegistrar { if (context.diagnostics.forElement(element).any { it.factory == Errors.UNNECESSARY_NOT_NULL_ASSERTION }) { exclExclExpr.replace(baseExpression) } - } + }, + RemoveForExpressionLoopParameterTypeProcessing() ) ) @@ -790,6 +814,17 @@ object NewJ2KPostProcessingRegistrar { override val writeActionNeeded: Boolean = true } + private class RemoveForExpressionLoopParameterTypeProcessing : CheckableProcessing(KtForExpression::class) { + override fun check(element: KtForExpression, settings: ConverterSettings?): Boolean = + element.loopParameter?.typeReference?.typeElement != null + && settings?.specifyLocalVariableTypeByDefault != true + + override fun action(element: KtForExpression) { + element.loopParameter?.typeReference = null + } + + } + private class RemoveRedundantExpressionQualifierProcessing : NewJ2kPostProcessing { private fun check(qualifiedExpression: KtQualifiedExpression): Boolean { val qualifier = (qualifiedExpression.receiverExpression as? KtNameReferenceExpression) diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraintsCollector.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraintsCollector.kt index 0b57507eed4..b88411fb830 100644 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraintsCollector.kt +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/nullabilityAnalysis/constraintsCollector.kt @@ -19,8 +19,11 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.asAssignment import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType import org.jetbrains.kotlin.resolve.bindingContextUtil.getTargetFunction +import org.jetbrains.kotlin.resolve.calls.callUtil.getType import org.jetbrains.kotlin.resolve.calls.components.isVararg import org.jetbrains.kotlin.resolve.descriptorUtil.firstOverridden +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered import org.jetbrains.kotlin.utils.addToStdlib.safeAs internal class ConstraintsCollector( @@ -97,10 +100,6 @@ internal class ConstraintsCollector( expression.condition?.addEqualsNullabilityConstraint(Nullability.NOT_NULL, ConstraintCameFrom.INITIALIZER) } - expression is KtForExpression -> { - expression.loopRange?.addEqualsNullabilityConstraint(Nullability.NOT_NULL, ConstraintCameFrom.INITIALIZER) - } - expression is KtCallExpression -> { collectConstraintsForCallExpression( expression, @@ -123,6 +122,40 @@ internal class ConstraintsCollector( expression is KtNamedFunction -> { collectSuperDeclarationConstraintsForFunction(expression) } + + expression is KtForExpression -> { + expression.loopRange?.addEqualsNullabilityConstraint(Nullability.NOT_NULL, ConstraintCameFrom.INITIALIZER) + + val loopParameterTypeVariable = + expression.loopParameter?.typeReference?.typeElement?.let { typeElement -> + analysisContext.typeElementToTypeVariable[typeElement] + } + if (loopParameterTypeVariable != null) { + val loopRangeBoundType = boundTypeStorage.boundTypeFor(expression.loopRange!!) + val loopRangeType = expression.loopRange?.getType(expression.analyze()) ?: return + val loopRangeItemType = loopRangeType + .constructor + .declarationDescriptor + ?.safeAs() + ?.getMemberScope(loopRangeType.arguments) + ?.getDescriptorsFiltered(DescriptorKindFilter.FUNCTIONS) { + it.asString() == "iterator" + }?.filterIsInstance() + ?.firstOrNull { it.valueParameters.isEmpty() } + ?.original + ?.returnType + ?: return + val boundType = boundTypeStorage.boundTypeForType( + loopRangeItemType, + loopRangeBoundType, + emptyMap() + ) ?: return + loopParameterTypeVariable.addEqualsNullabilityConstraint( + boundType.typeParameters.firstOrNull()?.boundType ?: return, + ConstraintCameFrom.ASSIGNMENT_TARGET + ) + } + } } Unit } diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/ConversionsRunner.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/ConversionsRunner.kt index 29f46663651..a6767152fb5 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/ConversionsRunner.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/ConversionsRunner.kt @@ -17,9 +17,7 @@ package org.jetbrains.kotlin.nj2k import org.jetbrains.kotlin.nj2k.conversions.* -import org.jetbrains.kotlin.nj2k.tree.JKTreeElement import org.jetbrains.kotlin.nj2k.tree.JKTreeRoot -import org.jetbrains.kotlin.nj2k.tree.impl.JKTreeRootImpl object ConversionsRunner { @@ -68,7 +66,6 @@ object ConversionsRunner { +LiteralConversion() +ForConversion(context) +LabeledStatementConversion() - +ForInConversion(context) +TypeParametersNullabilityConversion() +ArrayOperationsConversion(context) +BuiltinMembersConversion(context) diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/ForInConversion.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/ForInConversion.kt deleted file mode 100644 index 7d3db6f448a..00000000000 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/ForInConversion.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.nj2k.conversions - -import com.intellij.psi.* -import org.jetbrains.kotlin.nj2k.* -import org.jetbrains.kotlin.nj2k.tree.* -import org.jetbrains.kotlin.nj2k.tree.impl.* -import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.name.ClassId - - -class ForInConversion(private val context: ConversionContext) : RecursiveApplicableConversionBase() { - override fun applyToElement(element: JKTreeElement): JKTreeElement { - if (element !is JKForInStatement) return recurse(element) - - val parameterDeclaration = element.declaration as? JKVariable - if (parameterDeclaration != null - && !context.converter.settings.specifyLocalVariableTypeByDefault - ) { - parameterDeclaration.type = JKTypeElementImpl(JKNoTypeImpl) - } - - return recurse(element) - } -} \ No newline at end of file diff --git a/nj2k/testData/nullabilityAnalysis/loops.kt b/nj2k/testData/nullabilityAnalysis/loops.kt new file mode 100644 index 00000000000..3ef8700fd80 --- /dev/null +++ b/nj2k/testData/nullabilityAnalysis/loops.kt @@ -0,0 +1,16 @@ +fun bar(map: HashMap, list1: List, list2: List) { + for (entry: MutableMap.MutableEntry in map.entries) { + val value: Int = entry.value + if (entry.key == null) { + println(value + 1) + } + } + + for (i: Int in list1) { + i + 1 + } + + for (i: String in list2) { + i == null + } +} \ No newline at end of file diff --git a/nj2k/testData/nullabilityAnalysis/loops.kt.after b/nj2k/testData/nullabilityAnalysis/loops.kt.after new file mode 100644 index 00000000000..ee995b10c67 --- /dev/null +++ b/nj2k/testData/nullabilityAnalysis/loops.kt.after @@ -0,0 +1,16 @@ +fun bar(map: HashMap, list1: List, list2: List) { + for (entry: MutableMap.MutableEntry in map.entries) { + val value: Int = entry.value + if (entry.key == null) { + println(value + 1) + } + } + + for (i: Int in list1) { + i + 1 + } + + for (i: String? in list2) { + i == null + } +} \ No newline at end of file diff --git a/nj2k/tests/org/jetbrains/kotlin/nj2k/NullabilityAnalysisTestGenerated.java b/nj2k/tests/org/jetbrains/kotlin/nj2k/NullabilityAnalysisTestGenerated.java index fd84d412ecb..bcc6ae7bae2 100644 --- a/nj2k/tests/org/jetbrains/kotlin/nj2k/NullabilityAnalysisTestGenerated.java +++ b/nj2k/tests/org/jetbrains/kotlin/nj2k/NullabilityAnalysisTestGenerated.java @@ -44,6 +44,11 @@ public class NullabilityAnalysisTestGenerated extends AbstractNullabilityAnalysi runTest("nj2k/testData/nullabilityAnalysis/functionTypeParameterNullability.kt"); } + @TestMetadata("loops.kt") + public void testLoops() throws Exception { + runTest("nj2k/testData/nullabilityAnalysis/loops.kt"); + } + @TestMetadata("nullAsAssignment.kt") public void testNullAsAssignment() throws Exception { runTest("nj2k/testData/nullabilityAnalysis/nullAsAssignment.kt");