Emit not-null assertions for enhanced types

This commit is contained in:
Denis Zharkov
2015-07-14 14:21:20 +03:00
parent e23c7f457b
commit d19cb747be
11 changed files with 231 additions and 33 deletions
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.load.java.JavaVisibilities;
import org.jetbrains.kotlin.load.java.JvmAbi;
import org.jetbrains.kotlin.load.java.JvmAnnotationNames;
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor;
import org.jetbrains.kotlin.jvm.RuntimeAssertionInfo;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.annotations.AnnotationsPackage;
@@ -45,7 +46,6 @@ import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
import org.jetbrains.kotlin.resolve.jvm.JvmPackage;
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.kotlin.types.Approximation;
import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.TypesPackage;
import org.jetbrains.org.objectweb.asm.*;
@@ -637,10 +637,10 @@ public class AsmUtil {
public static StackValue genNotNullAssertions(
@NotNull GenerationState state,
@NotNull final StackValue stackValue,
@Nullable final Approximation.Info approximationInfo
@Nullable final RuntimeAssertionInfo runtimeAssertionInfo
) {
if (!state.isCallAssertionsEnabled()) return stackValue;
if (approximationInfo == null || !TypesPackage.assertNotNull(approximationInfo)) return stackValue;
if (runtimeAssertionInfo == null || !runtimeAssertionInfo.getNeedNotNullAssertion()) return stackValue;
return new StackValue(stackValue.type) {
@@ -649,7 +649,7 @@ public class AsmUtil {
stackValue.put(type, v);
if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
v.dup();
v.visitLdcInsn(approximationInfo.getMessage());
v.visitLdcInsn(runtimeAssertionInfo.getMessage());
v.invokestatic("kotlin/jvm/internal/Intrinsics", "checkExpressionValueIsNotNull",
"(Ljava/lang/Object;Ljava/lang/String;)V", false);
}
@@ -49,10 +49,12 @@ import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.impl.ScriptCodeDescriptor;
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.jvm.bindingContextSlices.BindingContextSlicesPackage;
import org.jetbrains.kotlin.lexer.JetTokens;
import org.jetbrains.kotlin.load.java.JvmAbi;
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor;
import org.jetbrains.kotlin.load.java.descriptors.SamConstructorDescriptor;
import org.jetbrains.kotlin.jvm.RuntimeAssertionInfo;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
@@ -75,7 +77,6 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature;
import org.jetbrains.kotlin.resolve.scopes.receivers.*;
import org.jetbrains.kotlin.types.Approximation;
import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.TypeProjection;
import org.jetbrains.kotlin.types.TypeUtils;
@@ -280,12 +281,12 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
StackValue stackValue = selector.accept(visitor, receiver);
Approximation.Info approximationInfo = null;
RuntimeAssertionInfo runtimeAssertionInfo = null;
if (selector instanceof JetExpression) {
approximationInfo = bindingContext.get(BindingContext.EXPRESSION_RESULT_APPROXIMATION, (JetExpression) selector);
runtimeAssertionInfo = bindingContext.get(BindingContextSlicesPackage.getRUNTIME_ASSERTION_INFO(), (JetExpression) selector);
}
return genNotNullAssertions(state, stackValue, approximationInfo);
return genNotNullAssertions(state, stackValue, runtimeAssertionInfo);
}
catch (ProcessCanceledException e) {
throw e;
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.jvm.RuntimeAssertionInfo;
import org.jetbrains.kotlin.load.kotlin.nativeDeclarations.NativeDeclarationsPackage;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.JetNamedFunction;
@@ -51,8 +52,6 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature;
import org.jetbrains.kotlin.types.Approximation;
import org.jetbrains.kotlin.types.TypesPackage;
import org.jetbrains.org.objectweb.asm.AnnotationVisitor;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
@@ -834,10 +833,10 @@ public class FunctionCodegen {
StackValue stackValue = AsmUtil.genNotNullAssertions(
state,
StackValue.onStack(delegateToMethod.getReturnType()),
TypesPackage.getApproximationTo(
delegatedTo.getReturnType(),
RuntimeAssertionInfo.create(
delegateFunction.getReturnType(),
new Approximation.DataFlowExtras.OnlyMessage(delegatedTo.getName() + "(...)")
delegatedTo.getReturnType(),
new RuntimeAssertionInfo.DataFlowExtras.OnlyMessage(delegatedTo.getName() + "(...)")
)
);
@@ -0,0 +1,116 @@
/*
* 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.jvm
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.jvm.bindingContextSlices.RUNTIME_ASSERTION_INFO
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.JetTypeChecker
import org.jetbrains.kotlin.types.upperIfFlexible
import kotlin.platform.platformStatic
public class RuntimeAssertionInfo(public val needNotNullAssertion: Boolean, public val message: String) {
public interface DataFlowExtras {
class OnlyMessage(message: String) : DataFlowExtras {
override val canBeNull: Boolean get() = true
override val possibleTypes: Set<JetType> get() = setOf()
override val presentableText: String = message
}
val canBeNull: Boolean
val possibleTypes: Set<JetType>
val presentableText: String
}
companion object {
platformStatic public fun create(
expectedType: JetType,
expressionType: JetType,
dataFlowExtras: DataFlowExtras
): RuntimeAssertionInfo? {
fun assertNotNull(): Boolean {
if (expectedType.isError() || expressionType.isError()) return false
// T : Any, T! = T..T?
// Let T$ will be copy of T! with enhanced nullability.
// Cases when nullability assertion needed: T! -> T, T$ -> T
// Expected type either T?, T! or T$
if (TypeUtils.isNullableType(expectedType) || expectedType.hasEnhancedNullability()) return false
// Expression type is not nullable and not enhanced (neither T?, T! or T$)
val isExpressionTypeNullable = TypeUtils.isNullableType(expressionType)
if (!isExpressionTypeNullable && !expressionType.hasEnhancedNullability()) return false
// Smart-cast T! or T?
if (!dataFlowExtras.canBeNull && isExpressionTypeNullable) return false
return true
}
return if (assertNotNull())
RuntimeAssertionInfo(needNotNullAssertion = true, message = dataFlowExtras.presentableText)
else
null
}
private fun JetType.hasEnhancedNullability()
= getAnnotations().findAnnotation(JvmAnnotationNames.ENHANCED_NULLABILITY_ANNOTATION) != null
}
}
public object RuntimeAssertionsTypeChecker : AdditionalTypeChecker {
override fun checkType(expression: JetExpression, expressionType: JetType, c: ResolutionContext<*>) {
if (TypeUtils.noExpectedType(c.expectedType)) return
val assertionInfo = RuntimeAssertionInfo.create(
c.expectedType,
expressionType,
object : RuntimeAssertionInfo.DataFlowExtras {
override val canBeNull: Boolean
get() = c.dataFlowInfo.getNullability(dataFlowValue).canBeNull()
override val possibleTypes: Set<JetType>
get() = c.dataFlowInfo.getPossibleTypes(dataFlowValue)
override val presentableText: String
get() = StringUtil.trimMiddle(expression.getText(), 50)
private val dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, expressionType, c)
}
)
if (assertionInfo != null) {
c.trace.record(RUNTIME_ASSERTION_INFO, expression, assertionInfo)
}
}
override fun checkReceiver(
receiverParameter: ReceiverParameterDescriptor,
receiverArgument: ReceiverValue,
safeAccess: Boolean,
c: CallResolutionContext<*>
) { }
}
@@ -0,0 +1,26 @@
/*
* 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.jvm.bindingContextSlices
import org.jetbrains.kotlin.jvm.RuntimeAssertionInfo
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.util.slicedMap.BasicWritableSlice
import org.jetbrains.kotlin.util.slicedMap.RewritePolicy
import org.jetbrains.kotlin.util.slicedMap.WritableSlice
import org.jetbrains.kotlin.utils.DO_NOTHING
public val RUNTIME_ASSERTION_INFO: WritableSlice<JetExpression, RuntimeAssertionInfo> = BasicWritableSlice(RewritePolicy.DO_NOTHING)
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.cfg.WhenChecker
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.jvm.RuntimeAssertionsTypeChecker
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.load.java.lazy.types.isMarkedNotNull
import org.jetbrains.kotlin.load.java.lazy.types.isMarkedNullable
@@ -63,7 +64,7 @@ public class KotlinJvmCheckerProvider(private val module: ModuleDescriptor) : Ad
JavaAnnotationMethodCallChecker(), TraitDefaultMethodCallChecker(),
ReflectionAPICallChecker(module)),
additionalTypeCheckers = listOf(JavaNullabilityWarningsChecker()),
additionalTypeCheckers = listOf(JavaNullabilityWarningsChecker(), RuntimeAssertionsTypeChecker),
additionalSymbolUsageValidators = listOf()
)
@@ -45,10 +45,10 @@ fun checkAssertions(illegalStateExpected: Boolean) {
check("plus") { A() + A() }
// field
// check("NULL") { A().NULL }
check("NULL") { A().NULL }
// static field
// check("STATIC_NULL") { A.STATIC_NULL }
check("STATIC_NULL") { A.STATIC_NULL }
// postfix expression
// TODO:
@@ -126,6 +126,7 @@ public class GenerateNotNullAssertionsTest extends CodegenTestCase {
loadFile("notNullAssertions/arrayListGet.kt");
String text = generateToText();
assertTrue(text.contains("checkExpressionValueIsNotNull"));
assertTrue(text.contains("checkParameterIsNotNull"));
}
@@ -136,7 +137,7 @@ public class GenerateNotNullAssertionsTest extends CodegenTestCase {
loadFile("notNullAssertions/javaMultipleSubstitutions.kt");
String text = generateToText();
assertEquals(0, StringUtil.getOccurrenceCount(text, "checkExpressionValueIsNotNull"));
assertEquals(3, StringUtil.getOccurrenceCount(text, "checkExpressionValueIsNotNull"));
assertEquals(3, StringUtil.getOccurrenceCount(text, "checkParameterIsNotNull"));
}
@@ -46,6 +46,10 @@ public final class JvmAnnotationNames {
public static final FqName PURELY_IMPLEMENTS_ANNOTATION = new FqName("kotlin.jvm.PurelyImplements");
// Just for internal use: there is no such real classes in bytecode
public static final FqName ENHANCED_NULLABILITY_ANNOTATION = new FqName("kotlin.jvm.internal.EnhancedNullability");
public static final FqName ENHANCED_MUTABILITY_ANNOTATION = new FqName("kotlin.jvm.internal.EnhancedMutability");
public static class KotlinClass {
public static final JvmClassName CLASS_NAME = JvmClassName.byInternalName("kotlin/jvm/internal/KotlinClass");
public static final ClassId KIND_CLASS_ID =
@@ -18,12 +18,19 @@ package org.jetbrains.kotlin.load.java.typeEnhacement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.annotations.CompositeAnnotations
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.java.typeEnhacement.JavaTypeQualifiers
import org.jetbrains.kotlin.load.java.typeEnhacement.MutabilityQualifier.MUTABLE
import org.jetbrains.kotlin.load.java.typeEnhacement.MutabilityQualifier.READ_ONLY
import org.jetbrains.kotlin.load.java.typeEnhacement.NullabilityQualifier.NOT_NULL
import org.jetbrains.kotlin.load.java.typeEnhacement.NullabilityQualifier.NULLABLE
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.types.*
// The index in the lambda is the position of the type component:
@@ -68,7 +75,7 @@ private fun JetType.enhanceInflexible(qualifiers: (Int) -> JavaTypeQualifiers, i
?: return Result(this, 1)
val effectiveQualifiers = qualifiers(index)
val enhancedClassifier = originalClass.enhanceMutability(effectiveQualifiers, position)
val (enhancedClassifier, enhancedMutabilityAnnotations) = originalClass.enhanceMutability(effectiveQualifiers, position)
var globalArgIndex = index + 1
val enhancedArguments = getArguments().mapIndexed {
@@ -87,10 +94,17 @@ private fun JetType.enhanceInflexible(qualifiers: (Int) -> JavaTypeQualifiers, i
}
}
val enhancedType = JetTypeImpl(
val (enhancedNullability, enhancedNullabilityAnnotations) = this.getEnhancedNullability(effectiveQualifiers, position)
val newAnnotations = listOf(
getAnnotations(),
enhancedMutabilityAnnotations,
enhancedNullabilityAnnotations
).filterNotNull().compositeAnnotationsOrSingle()
val enhancedType = JetTypeImpl(
newAnnotations,
enhancedClassifier.getTypeConstructor(),
this.getEnhancedNullability(effectiveQualifiers, position),
enhancedNullability,
enhancedArguments,
if (enhancedClassifier is ClassDescriptor)
enhancedClassifier.getMemberScope(enhancedArguments)
@@ -99,36 +113,72 @@ private fun JetType.enhanceInflexible(qualifiers: (Int) -> JavaTypeQualifiers, i
return Result(enhancedType, globalArgIndex - index)
}
private fun List<Annotations>.compositeAnnotationsOrSingle() = when (size()) {
0 -> error("At least one Annotations object expected")
1 -> single()
else -> CompositeAnnotations(this)
}
private fun TypeComponentPosition.shouldEnhance() = this != TypeComponentPosition.INFLEXIBLE
private fun ClassifierDescriptor.enhanceMutability(qualifiers: JavaTypeQualifiers, position: TypeComponentPosition): ClassifierDescriptor {
if (!position.shouldEnhance()) return this
if (this !is ClassDescriptor) return this // mutability is not applicable for type parameters
private data class EnhancementResult<T>(val result: T, val enhancementAnnotations: Annotations?)
private fun <T> T.noChange() = EnhancementResult(this, null)
private fun <T> T.enhancedNullability() = EnhancementResult(this, ENHANCED_NULLABILITY_ANNOTATIONS)
private fun <T> T.enhancedMutability() = EnhancementResult(this, ENHANCED_MUTABILITY_ANNOTATIONS)
private fun ClassifierDescriptor.enhanceMutability(qualifiers: JavaTypeQualifiers, position: TypeComponentPosition): EnhancementResult<ClassifierDescriptor> {
if (!position.shouldEnhance()) return this.noChange()
if (this !is ClassDescriptor) return this.noChange() // mutability is not applicable for type parameters
val mapping = JavaToKotlinClassMap.INSTANCE
when (qualifiers.mutability) {
READ_ONLY -> {
if (position == TypeComponentPosition.FLEXIBLE_LOWER && mapping.isMutable(this)) {
return mapping.convertMutableToReadOnly(this)
return mapping.convertMutableToReadOnly(this).enhancedMutability()
}
}
MUTABLE -> {
if (position == TypeComponentPosition.FLEXIBLE_UPPER && mapping.isReadOnly(this) ) {
return mapping.convertReadOnlyToMutable(this)
return mapping.convertReadOnlyToMutable(this).enhancedMutability()
}
}
}
return this
return this.noChange()
}
private fun JetType.getEnhancedNullability(qualifiers: JavaTypeQualifiers, position: TypeComponentPosition): Boolean {
if (!position.shouldEnhance()) return this.isMarkedNullable()
private fun JetType.getEnhancedNullability(qualifiers: JavaTypeQualifiers, position: TypeComponentPosition): EnhancementResult<Boolean> {
if (!position.shouldEnhance()) return this.isMarkedNullable().noChange()
return when (qualifiers.nullability) {
NULLABLE -> true
NOT_NULL -> false
else -> this.isMarkedNullable()
NULLABLE -> true.enhancedNullability()
NOT_NULL -> false.enhancedNullability()
else -> this.isMarkedNullable().noChange()
}
}
private val ENHANCED_NULLABILITY_ANNOTATIONS = EnhancedTypeAnnotations(JvmAnnotationNames.ENHANCED_NULLABILITY_ANNOTATION)
private val ENHANCED_MUTABILITY_ANNOTATIONS = EnhancedTypeAnnotations(JvmAnnotationNames.ENHANCED_MUTABILITY_ANNOTATION)
private class EnhancedTypeAnnotations(private val fqNameToMatch: FqName) : Annotations {
override fun isEmpty() = false
override fun findAnnotation(fqName: FqName) = when (fqName) {
fqNameToMatch -> EnhancedTypeAnnotationDescriptor
else -> null
}
override fun findExternalAnnotation(fqName: FqName) = null
// Note, that this class may break Annotations contract (!isEmpty && iterator.isEmpty())
// It's a hack that we need unless we have stable "user data" in JetType
override fun iterator(): Iterator<AnnotationDescriptor> = emptyList<AnnotationDescriptor>().iterator()
}
private object EnhancedTypeAnnotationDescriptor : AnnotationDescriptor {
private fun throwError() = error("No methods should be called on this descriptor. Only it's presence matters")
override fun getType() = throwError()
override fun getAllValueArguments() = throwError()
override fun toString() = "[EnhancedType]"
}
@@ -4,9 +4,9 @@ Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socke
stepIntoSpecificKotlinClasses.kt:8
MyJavaClass.java:12
stepIntoSpecificKotlinClasses.kt:8
Intrinsics.!EXT!
stepIntoSpecificKotlinClasses.kt:8
stepIntoSpecificKotlinClasses.kt:9
stepIntoSpecificKotlinClasses.kt:10
Thread.!EXT!
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0