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:
+80
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+112
@@ -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
|
||||
+67
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -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)
|
||||
}
|
||||
}
|
||||
+56
@@ -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);
|
||||
|
||||
+3
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user