Effects: add parsing of contracts from PSI

- Introduce part of effect system responsible for parsing
contracts which were expressed in sources using DSL from stdlib

- Add new errors to Errors.java related to contracts and corresponding
messages.

==========
Effect System introduction: 5/18
This commit is contained in:
Dmitry Savvinov
2017-10-03 15:02:31 +03:00
parent 32d80f322e
commit a46ccdbae1
11 changed files with 645 additions and 0 deletions
@@ -0,0 +1,80 @@
/*
* 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.AnalysisFlag
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.contracts.description.ContractDescription
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
class ContractParsingServices(val languageVersionSettings: LanguageVersionSettings) {
fun fastCheckIfContractPresent(element: KtElement): Boolean {
if (!isContractAllowedHere(element)) return false
val firstExpression = ((element as? KtFunction)?.bodyExpression as? KtBlockExpression)?.statements?.firstOrNull() ?: return false
return isContractDescriptionCallFastCheck(firstExpression)
}
fun checkContractAndRecordIfPresent(expression: KtExpression, trace: BindingTrace, scope: LexicalScope, isFirstStatement: Boolean) {
val ownerDescriptor = scope.ownerDescriptor
if (!isContractDescriptionCallFastCheck(expression) || ownerDescriptor !is FunctionDescriptor) return
val contractProvider = ownerDescriptor.getUserData(ContractProviderKey) ?: return
val isFeatureTurnedOn = languageVersionSettings.supportsFeature(LanguageFeature.CallsInPlaceEffect) ||
languageVersionSettings.supportsFeature(LanguageFeature.ReturnsEffect)
val contractDescriptor = when {
!isFeatureTurnedOn || !isContractDescriptionCallPreciseCheck(expression, trace.bindingContext) -> null
!isContractAllowedHere(scope) || !isFirstStatement -> {
trace.report(Errors.CONTRACT_NOT_ALLOWED.on(expression))
null
}
else -> parseContract(expression, trace, ownerDescriptor)
}
contractProvider.setContractDescription(contractDescriptor)
}
private fun parseContract(expression: KtExpression?, trace: BindingTrace, ownerDescriptor: FunctionDescriptor): ContractDescription? =
PsiContractParserDispatcher(trace, this).parseContract(expression, ownerDescriptor)
internal fun isContractDescriptionCall(expression: KtExpression, context: BindingContext): Boolean =
isContractDescriptionCallFastCheck(expression) && isContractDescriptionCallPreciseCheck(expression, context)
private fun isContractAllowedHere(element: KtElement): Boolean =
element is KtNamedFunction && element.isTopLevel && element.hasBlockBody() && !element.hasModifier(KtTokens.OPERATOR_KEYWORD)
private fun isContractAllowedHere(scope: LexicalScope): Boolean =
scope.kind == LexicalScopeKind.CODE_BLOCK && (scope.parent as? LexicalScope)?.kind == LexicalScopeKind.FUNCTION_INNER_SCOPE
private fun isContractDescriptionCallFastCheck(expression: KtExpression): Boolean =
expression is KtCallExpression && expression.calleeExpression?.text == "contract"
private fun isContractDescriptionCallPreciseCheck(expression: KtExpression, context: BindingContext): Boolean =
expression.getResolvedCall(context)?.resultingDescriptor?.isContractCallDescriptor() ?: false
}
@@ -0,0 +1,111 @@
/*
* 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.contracts.description.*
import org.jetbrains.kotlin.contracts.description.expressions.*
import org.jetbrains.kotlin.descriptors.ValueDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
internal class PsiConditionParser(val trace: BindingTrace, val dispatcher: PsiContractParserDispatcher) : KtVisitor<BooleanExpression?, Unit>() {
override fun visitIsExpression(expression: KtIsExpression, data: Unit): BooleanExpression? {
val variable = dispatcher.parseVariable(expression.leftHandSide) ?: return null
val typeReference = expression.typeReference ?: return null
val type = trace[BindingContext.TYPE, typeReference] ?: return null
return IsInstancePredicate(variable, type, expression.isNegated)
}
override fun visitKtElement(element: KtElement, data: Unit): BooleanExpression? {
val resolvedCall = element.getResolvedCall(trace.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
if (left is ConstantReference && left == ConstantReference.NULL && right is VariableReference) {
return IsNullPredicate(right, isNegated)
}
if (right is ConstantReference && right == ConstantReference.NULL && left is VariableReference) {
return IsNullPredicate(left, isNegated)
}
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(element, "only equality comparisons with 'null' allowed"))
return null
}
else -> {
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(element, "unsupported construction"))
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? {
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(expression, "call-expressions are not supported yet"))
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
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)
}
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) {
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(expression.baseExpression!!, "negations in contract description can be applied only to variables/values"))
}
return LogicalNot(arg)
}
override fun visitParenthesizedExpression(expression: KtParenthesizedExpression, data: Unit): BooleanExpression? =
KtPsiUtil.deparenthesize(expression)?.accept(this, data)
}
@@ -0,0 +1,46 @@
/*
* 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.contracts.description.expressions.BooleanConstantReference
import org.jetbrains.kotlin.contracts.description.expressions.ConstantReference
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtVisitor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.types.KotlinType
internal class PsiConstantParser(val trace: BindingTrace) : KtVisitor<ConstantReference?, Unit>() {
override fun visitKtElement(element: KtElement, data: Unit?): ConstantReference? = null
override fun visitConstantExpression(expression: KtConstantExpression, data: Unit?): ConstantReference? {
val type: KotlinType = trace.getType(expression) ?: return null
val compileTimeConstant: CompileTimeConstant<*>
= trace.get(BindingContext.COMPILE_TIME_VALUE, expression) ?: return null
val value: Any? = compileTimeConstant.getValue(type)
return when (value) {
true -> BooleanConstantReference.TRUE
false -> BooleanConstantReference.FALSE
null -> ConstantReference.NULL
else -> null
}
}
}
@@ -0,0 +1,112 @@
/*
* 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.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.contracts.description.BooleanExpression
import org.jetbrains.kotlin.contracts.description.ContractDescription
import org.jetbrains.kotlin.contracts.description.EffectDeclaration
import org.jetbrains.kotlin.contracts.description.expressions.BooleanVariableReference
import org.jetbrains.kotlin.contracts.description.expressions.ConstantReference
import org.jetbrains.kotlin.contracts.description.expressions.ContractDescriptionValue
import org.jetbrains.kotlin.contracts.description.expressions.VariableReference
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.CALLS_IN_PLACE_EFFECT
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.CONDITIONAL_EFFECT
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.RETURNS_EFFECT
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.RETURNS_NOT_NULL_EFFECT
import org.jetbrains.kotlin.contracts.parsing.effects.PsiCallsEffectParser
import org.jetbrains.kotlin.contracts.parsing.effects.PsiConditionalEffectParser
import org.jetbrains.kotlin.contracts.parsing.effects.PsiReturnsEffectParser
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
internal class PsiContractParserDispatcher(val trace: BindingTrace, val contractParsingServices: ContractParsingServices) {
private val conditionParser = PsiConditionParser(trace, this)
private val constantParser = PsiConstantParser(trace)
private val effectsParsers: Map<Name, PsiEffectParser> = mapOf(
RETURNS_EFFECT to PsiReturnsEffectParser(trace, this),
RETURNS_NOT_NULL_EFFECT to PsiReturnsEffectParser(trace, this),
CALLS_IN_PLACE_EFFECT to PsiCallsEffectParser(trace, this),
CONDITIONAL_EFFECT to PsiConditionalEffectParser(trace, this)
)
fun parseContract(expression: KtExpression?, ownerDescriptor: FunctionDescriptor): ContractDescription? {
if (expression == null) return null
if (!contractParsingServices.isContractDescriptionCall(expression, trace.bindingContext)) return null
val resolvedCall = expression.getResolvedCall(trace.bindingContext)!! // Must be non-null due to 'isContractDescriptionCall' check
val lambda = resolvedCall.firstArgumentAsExpressionOrNull() as? KtLambdaExpression ?: return null
val effects = lambda.bodyExpression?.statements?.mapNotNull { parseEffect(it) } ?: return null
if (effects.isEmpty()) return null
return ContractDescription(effects, ownerDescriptor)
}
fun parseCondition(expression: KtExpression?): BooleanExpression? = expression?.accept(conditionParser, Unit)
fun parseEffect(expression: KtExpression?): EffectDeclaration? {
if (expression == null) return null
val returnType = expression.getType(trace.bindingContext) ?: return null
val parser = effectsParsers[returnType.constructor.declarationDescriptor?.name]
if (parser == null) {
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(expression, "Unrecognized effect"))
return null
}
return parser.tryParseEffect(expression)
}
fun parseConstant(expression: KtExpression?): ConstantReference? {
if (expression == null) return null
return expression.accept(constantParser, Unit)
}
fun parseVariable(expression: KtExpression?): VariableReference? {
if (expression == null) return null
val descriptor = expression.getResolvedCall(trace.bindingContext)?.resultingDescriptor ?: return null
if (descriptor !is ParameterDescriptor) {
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(expression, "only references to parameters are allowed in contract description"))
return null
}
if (descriptor is ReceiverParameterDescriptor && descriptor.type.constructor.declarationDescriptor?.isFromContractDsl() == true) {
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(expression, "only references to parameters are allowed. Did you miss label on <this>?"))
}
return if (KotlinBuiltIns.isBoolean(descriptor.type))
BooleanVariableReference(descriptor)
else
VariableReference(descriptor)
}
fun parseValue(expression: KtExpression?): ContractDescriptionValue? {
val variable = parseVariable(expression)
if (variable != null) return variable
return parseConstant(expression)
}
}
@@ -0,0 +1,92 @@
/*
* 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.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.CALLS_IN_PLACE
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.CONTRACT
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.CONTRACTS_DSL_ANNOTATION_FQN
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.EFFECT
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.IMPLIES
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.INVOCATION_KIND_ENUM
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.RETURNS
import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.RETURNS_NOT_NULL
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.types.typeUtil.isBoolean
import org.jetbrains.kotlin.types.typeUtil.isNullableAny
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object ContractsDslNames {
// Internal marker-annotation for distinguishing our API
val CONTRACTS_DSL_ANNOTATION_FQN = FqName("kotlin.internal.ContractsDsl")
// Types
val EFFECT = Name.identifier("Effect")
val CONDITIONAL_EFFECT = Name.identifier("ConditionalEffect")
val SIMPLE_EFFECT = Name.identifier("SimpleEffect")
val RETURNS_EFFECT = Name.identifier("Returns")
val RETURNS_NOT_NULL_EFFECT = Name.identifier("ReturnsNotNull")
val CALLS_IN_PLACE_EFFECT = Name.identifier("CallsInPlace")
// Structure-defining calls
val CONTRACT = Name.identifier("contract")
val IMPLIES = Name.identifier("implies")
// Effect-declaration calls
val RETURNS = Name.identifier("returns")
val RETURNS_NOT_NULL = Name.identifier("returnsNotNull")
val CALLS_IN_PLACE = Name.identifier("callsInPlace")
// enum class InvocationKind
val INVOCATION_KIND_ENUM = Name.identifier("InvocationKind")
val EXACTLY_ONCE_KIND = Name.identifier("EXACTLY_ONCE")
val AT_LEAST_ONCE_KIND = Name.identifier("AT_LEAST_ONCE")
val UNKNOWN_KIND = Name.identifier("UNKNOWN")
val AT_MOST_ONCE_KIND = Name.identifier("AT_MOST_ONCE")
}
fun DeclarationDescriptor.isFromContractDsl(): Boolean = this.annotations.hasAnnotation(CONTRACTS_DSL_ANNOTATION_FQN)
fun DeclarationDescriptor.isContractCallDescriptor(): Boolean = equalsDslDescriptor(CONTRACT)
fun DeclarationDescriptor.isImpliesCallDescriptor(): Boolean = equalsDslDescriptor(IMPLIES)
fun DeclarationDescriptor.isReturnsEffectDescriptor(): Boolean = equalsDslDescriptor(RETURNS)
fun DeclarationDescriptor.isReturnsNotNullDescriptor(): Boolean = equalsDslDescriptor(RETURNS_NOT_NULL)
fun DeclarationDescriptor.isEffectDescriptor(): Boolean = equalsDslDescriptor(EFFECT)
fun DeclarationDescriptor.isCallsInPlaceEffectDescriptor(): Boolean = equalsDslDescriptor(CALLS_IN_PLACE)
fun DeclarationDescriptor.isInvocationKindEnum(): Boolean = equalsDslDescriptor(INVOCATION_KIND_ENUM)
fun DeclarationDescriptor.isEqualsDescriptor(): Boolean =
this is FunctionDescriptor && this.name == Name.identifier("equals") && // fast checks
this.returnType?.isBoolean() == true && this.valueParameters.singleOrNull()?.type?.isNullableAny() == true // signature matches
internal fun ResolvedCall<*>.firstArgumentAsExpressionOrNull(): KtExpression? =
this.valueArgumentsByIndex?.firstOrNull()?.safeAs<ExpressionValueArgument>()?.valueArgument?.getArgumentExpression()
private fun DeclarationDescriptor.equalsDslDescriptor(dslName: Name): Boolean = this.name == dslName && this.isFromContractDsl()
@@ -0,0 +1,27 @@
/*
* 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.contracts.description.EffectDeclaration
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.BindingTrace
internal interface PsiEffectParser {
fun tryParseEffect(expression: KtExpression): EffectDeclaration?
}
internal abstract class AbstractPsiEffectParser(val trace: BindingTrace, val contractParserDispatcher: PsiContractParserDispatcher) : PsiEffectParser
@@ -0,0 +1,67 @@
/*
* 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.effects
import org.jetbrains.kotlin.contracts.description.CallsEffectDeclaration
import org.jetbrains.kotlin.contracts.description.EffectDeclaration
import org.jetbrains.kotlin.contracts.description.InvocationKind
import org.jetbrains.kotlin.contracts.parsing.*
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
internal class PsiCallsEffectParser(
trace: BindingTrace,
contractParserDispatcher: PsiContractParserDispatcher
) : AbstractPsiEffectParser(trace, contractParserDispatcher) {
override fun tryParseEffect(expression: KtExpression): EffectDeclaration? {
val resolvedCall = expression.getResolvedCall(trace.bindingContext) ?: return null
val descriptor = resolvedCall.resultingDescriptor
if (!descriptor.isCallsInPlaceEffectDescriptor()) return null
val lambda = contractParserDispatcher.parseVariable(resolvedCall.firstArgumentAsExpressionOrNull()) ?: return null
val kindArgument = resolvedCall.valueArgumentsByIndex?.getOrNull(1)
val kind = when (kindArgument) {
is DefaultValueArgument -> InvocationKind.UNKNOWN
is ExpressionValueArgument -> kindArgument.valueArgument?.getArgumentExpression()?.toInvocationKind(trace) ?: return null
else -> return null
}
return CallsEffectDeclaration(lambda, kind)
}
private fun KtExpression.toInvocationKind(trace: BindingTrace): InvocationKind? {
val descriptor = this.getResolvedCall(trace.bindingContext)?.resultingDescriptor ?: return null
if (!descriptor.parents.first().isInvocationKindEnum()) return null
return when (descriptor.fqNameSafe.shortName()) {
ContractsDslNames.AT_MOST_ONCE_KIND -> InvocationKind.AT_MOST_ONCE
ContractsDslNames.EXACTLY_ONCE_KIND -> InvocationKind.EXACTLY_ONCE
ContractsDslNames.AT_LEAST_ONCE_KIND -> InvocationKind.AT_LEAST_ONCE
ContractsDslNames.UNKNOWN_KIND -> InvocationKind.UNKNOWN
else -> null
}
}
}
@@ -0,0 +1,47 @@
/*
* 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.effects
import org.jetbrains.kotlin.contracts.description.ConditionalEffectDeclaration
import org.jetbrains.kotlin.contracts.description.EffectDeclaration
import org.jetbrains.kotlin.contracts.parsing.AbstractPsiEffectParser
import org.jetbrains.kotlin.contracts.parsing.PsiContractParserDispatcher
import org.jetbrains.kotlin.contracts.parsing.firstArgumentAsExpressionOrNull
import org.jetbrains.kotlin.contracts.parsing.isImpliesCallDescriptor
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal class PsiConditionalEffectParser(
trace: BindingTrace,
dispatcher: PsiContractParserDispatcher
) : AbstractPsiEffectParser(trace, dispatcher) {
override fun tryParseEffect(expression: KtExpression): EffectDeclaration? {
val resolvedCall = expression.getResolvedCall(trace.bindingContext) ?: return null
if (!resolvedCall.resultingDescriptor.isImpliesCallDescriptor()) return null
val effect = contractParserDispatcher.parseEffect(resolvedCall.dispatchReceiver.safeAs<ExpressionReceiver>()?.expression)
?: return null
val condition = contractParserDispatcher.parseCondition(resolvedCall.firstArgumentAsExpressionOrNull())
?: return null
return ConditionalEffectDeclaration(effect, condition)
}
}
@@ -0,0 +1,56 @@
/*
* 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.effects
import org.jetbrains.kotlin.contracts.description.EffectDeclaration
import org.jetbrains.kotlin.contracts.description.ReturnsEffectDeclaration
import org.jetbrains.kotlin.contracts.description.expressions.ConstantReference
import org.jetbrains.kotlin.contracts.parsing.*
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
internal class PsiReturnsEffectParser(
trace: BindingTrace,
contractParserDispatcher: PsiContractParserDispatcher
) : AbstractPsiEffectParser(trace, contractParserDispatcher) {
override fun tryParseEffect(expression: KtExpression): EffectDeclaration? {
val resolvedCall = expression.getResolvedCall(trace.bindingContext) ?: return null
val descriptor = resolvedCall.resultingDescriptor
if (descriptor.isReturnsNotNullDescriptor())
return ReturnsEffectDeclaration(ConstantReference.NOT_NULL)
if (!descriptor.isReturnsEffectDescriptor()) return null
val argumentExpression = resolvedCall.firstArgumentAsExpressionOrNull()
val constantValue = if (argumentExpression == null)
ConstantReference.WILDCARD
else {
// Note that we distinguish absence of an argument and unparsed argument
val constant = contractParserDispatcher.parseConstant(argumentExpression)
if (constant == null) {
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(argumentExpression, "only true/false/null constants in Returns-effect are currently supported"))
return null
}
constant
}
return ReturnsEffectDeclaration(constantValue)
}
}
@@ -983,6 +983,10 @@ public interface Errors {
DiagnosticFactory2<PsiElement, String, String> PLUGIN_WARNING = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<PsiElement, String, String> PLUGIN_INFO = DiagnosticFactory2.create(INFO);
// Function contracts
DiagnosticFactory1<KtElement, String> ERROR_IN_CONTRACT_DESCRIPTION = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<KtElement> CONTRACT_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
// Error sets
ImmutableSet<? extends DiagnosticFactory<?>> UNRESOLVED_REFERENCE_DIAGNOSTICS = ImmutableSet.of(
UNRESOLVED_REFERENCE, NAMED_PARAMETER_NOT_FOUND, UNRESOLVED_REFERENCE_WRONG_RECEIVER);
@@ -889,6 +889,9 @@ public class DefaultErrorMessages {
MAP.put(PLUGIN_WARNING, "{0}: {1}", TO_STRING, TO_STRING);
MAP.put(PLUGIN_INFO, "{0}: {1}", TO_STRING, TO_STRING);
MAP.put(ERROR_IN_CONTRACT_DESCRIPTION, "Error in contract description: {0}", TO_STRING);
MAP.put(CONTRACT_NOT_ALLOWED, "Contract is not allowed here");
MAP.setImmutable();
for (Field field : Errors.class.getFields()) {