org.jetbrains.kotlin.contracts.parsing.PsiConditionParser.kt Maven / Gradle / Ivy
/*
* 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.contracts.parsing
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.contracts.description.BooleanExpression
import org.jetbrains.kotlin.contracts.description.expressions.*
import org.jetbrains.kotlin.descriptors.ValueDescriptor
import org.jetbrains.kotlin.descriptors.impl.AbstractTypeParameterDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.inference.CapturedType
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.types.CastDiagnosticsUtil
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
internal class PsiConditionParser(
private val collector: ContractParsingDiagnosticsCollector,
private val callContext: ContractCallContext,
private val dispatcher: PsiContractParserDispatcher
) : KtVisitor() {
override fun visitIsExpression(expression: KtIsExpression, data: Unit): BooleanExpression? {
val variable = dispatcher.parseVariable(expression.leftHandSide) ?: return null
val typeReference = expression.typeReference ?: return null
val type = callContext.bindingContext[BindingContext.TYPE, typeReference]?.unwrap() ?: return null
val descriptor = type.constructor.declarationDescriptor
if (type is CapturedType) {
collector.badDescription("references to captured types are forbidden in contracts", typeReference)
return null
}
if (descriptor is AbstractTypeParameterDescriptor) {
val reifiedGenericsAllowed = callContext.languageVersionSettings.supportsFeature(LanguageFeature.AllowReifiedGenericsInContracts)
if (!reifiedGenericsAllowed || !descriptor.isReified) {
val message = if (reifiedGenericsAllowed) {
"references to not reified type parameters are forbidden in contracts"
} else {
"references to type parameters are forbidden in contracts"
}
collector.badDescription(message, typeReference)
return null
}
}
// This should be reported as "Can't check for erased" error, but we explicitly abort contract parsing. Just in case.
if (CastDiagnosticsUtil.isCastErased(variable.descriptor.type, type, KotlinTypeChecker.DEFAULT)) {
return null
}
return IsInstancePredicate(variable, type, expression.isNegated)
}
override fun visitKtElement(element: KtElement, data: Unit): BooleanExpression? {
val resolvedCall = element.getResolvedCall(callContext.bindingContext)
val descriptor = resolvedCall?.resultingDescriptor ?: return null
// boolean variable
if (descriptor is ValueDescriptor) {
val booleanVariable = dispatcher.parseVariable(element as? KtExpression) ?: return null
// we don't report type mismatch because it will be reported by the typechecker
return booleanVariable as? BooleanVariableReference
}
// operator
when {
descriptor.isEqualsDescriptor() -> {
val left = dispatcher.parseValue((resolvedCall.dispatchReceiver as? ExpressionReceiver)?.expression) ?: return null
val right = dispatcher.parseValue(resolvedCall.firstArgumentAsExpressionOrNull()) ?: return null
val isNegated = (element as? KtBinaryExpression)?.operationToken == KtTokens.EXCLEQ ?: false
return processEquals(left, right, isNegated, element)
}
else -> {
collector.badDescription("unsupported construction", element)
return null
}
}
}
override fun visitConstantExpression(expression: KtConstantExpression, data: Unit?): BooleanExpression? {
// we don't report type mismatch because it will be reported by the typechecker
return dispatcher.parseConstant(expression) as? BooleanConstantReference
}
override fun visitCallExpression(expression: KtCallExpression, data: Unit?): BooleanExpression? {
collector.badDescription("call-expressions are not supported yet", expression)
return null
}
override fun visitBinaryExpression(expression: KtBinaryExpression, data: Unit): BooleanExpression? {
val operationConstructor: (BooleanExpression, BooleanExpression) -> BooleanExpression
when (expression.operationToken) {
KtTokens.ANDAND -> operationConstructor = ::LogicalAnd
KtTokens.OROR -> operationConstructor = ::LogicalOr
KtTokens.EXCLEQEQEQ, KtTokens.EQEQEQ -> return parseIdentityEquals(expression)
else -> return super.visitBinaryExpression(expression, data) // pass binary expression further
}
val left = expression.left?.accept(this, data) ?: return null
val right = expression.right?.accept(this, data) ?: return null
return operationConstructor(left, right)
}
private fun parseIdentityEquals(expression: KtBinaryExpression): BooleanExpression? {
val lhs = dispatcher.parseValue(expression.left) ?: return null
val rhs = dispatcher.parseValue(expression.right) ?: return null
return processEquals(lhs, rhs, expression.operationToken == KtTokens.EXCLEQEQEQ, expression)
}
private fun processEquals(
left: ContractDescriptionValue,
right: ContractDescriptionValue,
isNegated: Boolean,
reportOn: KtElement
): BooleanExpression? {
return when {
left is ConstantReference && left == ConstantReference.NULL && right is VariableReference -> IsNullPredicate(right, isNegated)
right is ConstantReference && right == ConstantReference.NULL && left is VariableReference -> IsNullPredicate(left, isNegated)
else -> {
collector.badDescription("only equality comparisons with 'null' allowed", reportOn)
null
}
}
}
override fun visitUnaryExpression(expression: KtUnaryExpression, data: Unit): BooleanExpression? {
if (expression.operationToken != KtTokens.EXCL) return super.visitUnaryExpression(expression, data)
val arg = expression.baseExpression?.accept(this, data) ?: return null
if (arg !is ContractDescriptionValue) {
collector.badDescription(
"negations in contract description can be applied only to variables/values",
expression.baseExpression!!
)
}
return LogicalNot(arg)
}
override fun visitParenthesizedExpression(expression: KtParenthesizedExpression, data: Unit): BooleanExpression? =
KtPsiUtil.deparenthesize(expression)?.accept(this, data)
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy