diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/ContextInfoToDataFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/ContextInfoToDataFlowInfo.kt new file mode 100644 index 00000000000..28f2049f907 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/ContextInfoToDataFlowInfo.kt @@ -0,0 +1,66 @@ +/* + * 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 + +import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.contracts.model.ESValue +import org.jetbrains.kotlin.contracts.model.MutableContextInfo +import org.jetbrains.kotlin.contracts.model.structure.ESConstant +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue +import org.jetbrains.kotlin.resolve.calls.smartcasts.IdentifierInfo + +fun MutableContextInfo.toDataFlowInfo(languageVersionSettings: LanguageVersionSettings): DataFlowInfo { + var resultingDataFlowInfo = DataFlowInfoFactory.EMPTY + + extractDataFlowStatements(equalValues) { leftDfv, rightValue -> + val rightDfv = rightValue.toDataFlowValue() + if (rightDfv != null) { + resultingDataFlowInfo = resultingDataFlowInfo.equate(leftDfv, rightDfv, false, languageVersionSettings) + } + IntArray(42) { it } + } + + extractDataFlowStatements(notEqualValues) { leftDfv, rightValue -> + val rightDfv = rightValue.toDataFlowValue() + if (rightDfv != null) { + resultingDataFlowInfo = resultingDataFlowInfo.disequate(leftDfv, rightDfv, languageVersionSettings) + } + } + + extractDataFlowStatements(subtypes) { leftDfv, type -> + resultingDataFlowInfo = resultingDataFlowInfo.establishSubtyping(leftDfv, type, languageVersionSettings) + } + + return resultingDataFlowInfo +} + +inline private fun extractDataFlowStatements(dictionary: Map>, callback: (DataFlowValue, D) -> Unit) { + for ((key, setOfValues) in dictionary) { + val leftDfv = key.toDataFlowValue() ?: continue + setOfValues.forEach { callback(leftDfv, it) } + } +} + +private fun ESValue.toDataFlowValue(): DataFlowValue? = when (this) { + is ESDataFlowValue -> dataFlowValue + ESConstant.NULL -> DataFlowValue.nullValue(DefaultBuiltIns.Instance) + is ESConstant -> DataFlowValue(IdentifierInfo.NO, type) + else -> null +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/ESDataFlowValue.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/ESDataFlowValue.kt new file mode 100644 index 00000000000..421047c1476 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/ESDataFlowValue.kt @@ -0,0 +1,32 @@ +/* + * 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 + +import org.jetbrains.kotlin.contracts.model.ESValue +import org.jetbrains.kotlin.contracts.model.structure.ESVariable +import org.jetbrains.kotlin.contracts.model.ESExpressionVisitor +import org.jetbrains.kotlin.descriptors.ValueDescriptor +import org.jetbrains.kotlin.psi.KtLambdaExpression +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue + +class ESDataFlowValue(descriptor: ValueDescriptor, val dataFlowValue: DataFlowValue) : ESVariable(descriptor) + +class ESLambda(val lambda: KtLambdaExpression) : ESValue(null) { + override fun accept(visitor: ESExpressionVisitor): T { + throw IllegalStateException("Lambdas shouldn't be visited by ESExpressionVisitor") + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt new file mode 100644 index 00000000000..740410b8bfc --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt @@ -0,0 +1,124 @@ +/* + * 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 + +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.contracts.model.structure.ESCalls +import org.jetbrains.kotlin.contracts.model.structure.ESReturns +import org.jetbrains.kotlin.contracts.model.functors.EqualsFunctor +import org.jetbrains.kotlin.contracts.model.structure.ESConstant +import org.jetbrains.kotlin.contracts.model.structure.UNKNOWN_COMPUTATION +import org.jetbrains.kotlin.contracts.model.structure.lift +import org.jetbrains.kotlin.contracts.model.ESEffect +import org.jetbrains.kotlin.contracts.model.Computation +import org.jetbrains.kotlin.contracts.model.MutableContextInfo +import org.jetbrains.kotlin.contracts.model.visitors.InfoCollector +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.smartcasts.ConditionalDataFlowInfo +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo + +class EffectSystem(val languageVersionSettings: LanguageVersionSettings) { + + fun getDataFlowInfoForFinishedCall( + resolvedCall: ResolvedCall<*>, + bindingTrace: BindingTrace, + moduleDescriptor: ModuleDescriptor + ): DataFlowInfo { + if (!languageVersionSettings.supportsFeature(LanguageFeature.ReturnsEffect)) return DataFlowInfo.EMPTY + + // Prevent launch of effect system machinery on pointless cases (constants/enums/constructors/etc.) + val callExpression = resolvedCall.call.callElement as? KtCallExpression ?: return DataFlowInfo.EMPTY + if (callExpression is KtDeclaration) return DataFlowInfo.EMPTY + + val resultContextInfo = getContextInfoWhen(ESReturns(ESConstant.WILDCARD), callExpression, bindingTrace, moduleDescriptor) + + return resultContextInfo.toDataFlowInfo(languageVersionSettings) + } + + fun getDataFlowInfoWhenEquals( + leftExpression: KtExpression?, + rightExpression: KtExpression?, + bindingTrace: BindingTrace, + moduleDescriptor: ModuleDescriptor + ): ConditionalDataFlowInfo { + if (!languageVersionSettings.supportsFeature(LanguageFeature.ReturnsEffect)) return ConditionalDataFlowInfo.EMPTY + if (leftExpression == null || rightExpression == null) return ConditionalDataFlowInfo.EMPTY + + val leftComputation = getNonTrivialComputation(leftExpression, bindingTrace, moduleDescriptor) ?: return ConditionalDataFlowInfo.EMPTY + val rightComputation = getNonTrivialComputation(rightExpression, bindingTrace, moduleDescriptor) ?: return ConditionalDataFlowInfo.EMPTY + + val effects = EqualsFunctor(false).invokeWithArguments(leftComputation, rightComputation) + + val equalsContextInfo = InfoCollector(ESReturns(true.lift())).collectFromSchema(effects) + val notEqualsContextInfo = InfoCollector(ESReturns(false.lift())).collectFromSchema(effects) + + return ConditionalDataFlowInfo( + equalsContextInfo.toDataFlowInfo(languageVersionSettings), + notEqualsContextInfo.toDataFlowInfo(languageVersionSettings) + ) + } + + fun recordDefiniteInvocations(resolvedCall: ResolvedCall<*>, bindingTrace: BindingTrace, moduleDescriptor: ModuleDescriptor) { + if (!languageVersionSettings.supportsFeature(LanguageFeature.CallsInPlaceEffect)) return + + // Prevent launch of effect system machinery on pointless cases (constants/enums/constructors/etc.) + val callExpression = resolvedCall.call.callElement as? KtCallExpression ?: return + if (callExpression is KtDeclaration) return + + val resultingContextInfo = getContextInfoWhen(ESReturns(ESConstant.WILDCARD), callExpression, bindingTrace, moduleDescriptor) + for (effect in resultingContextInfo.firedEffects) { + val callsEffect = effect as? ESCalls ?: continue + val lambdaExpression = (callsEffect.callable as? ESLambda)?.lambda ?: continue + bindingTrace.record(BindingContext.LAMBDA_INVOCATIONS, lambdaExpression, callsEffect.kind) + } + } + + fun extractDataFlowInfoFromCondition( + condition: KtExpression?, + value: Boolean, + bindingTrace: BindingTrace, + moduleDescriptor: ModuleDescriptor + ): DataFlowInfo { + if (!languageVersionSettings.supportsFeature(LanguageFeature.ReturnsEffect)) return DataFlowInfo.EMPTY + if (condition == null) return DataFlowInfo.EMPTY + + return getContextInfoWhen(ESReturns(value.lift()), condition, bindingTrace, moduleDescriptor) + .toDataFlowInfo(languageVersionSettings) + } + + private fun getContextInfoWhen( + observedEffect: ESEffect, + expression: KtExpression, + bindingTrace: BindingTrace, + moduleDescriptor: ModuleDescriptor + ): MutableContextInfo { + val computation = getNonTrivialComputation(expression, bindingTrace, moduleDescriptor) ?: return MutableContextInfo.EMPTY + return InfoCollector(observedEffect).collectFromSchema(computation.effects) + } + + private fun getNonTrivialComputation(expression: KtExpression, trace: BindingTrace, moduleDescriptor: ModuleDescriptor): Computation? { + val computation = EffectsExtractingVisitor(trace, moduleDescriptor).extractOrGetCached(expression) + return if (computation == UNKNOWN_COMPUTATION) null else computation + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt new file mode 100644 index 00000000000..43d70efbe9a --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt @@ -0,0 +1,170 @@ +/* + * 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 + +import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.contracts.interpretation.ContractInterpretationDispatcher +import org.jetbrains.kotlin.contracts.model.Computation +import org.jetbrains.kotlin.contracts.model.Functor +import org.jetbrains.kotlin.contracts.model.functors.* +import org.jetbrains.kotlin.contracts.model.structure.CallComputation +import org.jetbrains.kotlin.contracts.model.structure.ESConstant +import org.jetbrains.kotlin.contracts.model.structure.UNKNOWN_COMPUTATION +import org.jetbrains.kotlin.contracts.model.structure.lift +import org.jetbrains.kotlin.contracts.parsing.isEqualsDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.ValueDescriptor +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.calls.model.ExpressionValueArgument +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory +import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind +import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant +import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.utils.addIfNotNull + +/** + * Visits a given PSI-tree of call (and nested calls, if any) and extracts information + * about effects of that call. + */ +class EffectsExtractingVisitor( + private val trace: BindingTrace, + private val moduleDescriptor: ModuleDescriptor +) : KtVisitor() { + fun extractOrGetCached(element: KtElement): Computation { + trace[BindingContext.EXPRESSION_EFFECTS, element]?.let { return it } + return element.accept(this, Unit).also { trace.record(BindingContext.EXPRESSION_EFFECTS, element, it) } + } + + override fun visitKtElement(element: KtElement, data: Unit): Computation { + val resolvedCall = element.getResolvedCall(trace.bindingContext) ?: return UNKNOWN_COMPUTATION + if (resolvedCall.isCallWithUnsupportedReceiver()) return UNKNOWN_COMPUTATION + + val arguments = resolvedCall.getCallArgumentsAsComputations() ?: return UNKNOWN_COMPUTATION + + val descriptor = resolvedCall.resultingDescriptor + return when { + descriptor.isEqualsDescriptor() -> CallComputation(DefaultBuiltIns.Instance.booleanType, EqualsFunctor(false).invokeWithArguments(arguments)) + descriptor is ValueDescriptor -> ESDataFlowValue(descriptor, (element as KtExpression).createDataFlowValue() ?: return UNKNOWN_COMPUTATION) + descriptor is FunctionDescriptor -> CallComputation(descriptor.returnType, descriptor.getFunctor()?.invokeWithArguments(arguments) ?: emptyList()) + else -> UNKNOWN_COMPUTATION + } + } + + // We need lambdas only as arguments currently, and it is processed in 'visitElement' while parsing arguments of call. + // For all other cases we don't need lambdas. + override fun visitLambdaExpression(expression: KtLambdaExpression, data: Unit?): Computation = UNKNOWN_COMPUTATION + + override fun visitParenthesizedExpression(expression: KtParenthesizedExpression, data: Unit): Computation = + KtPsiUtil.deparenthesize(expression)?.accept(this, data) ?: UNKNOWN_COMPUTATION + + override fun visitConstantExpression(expression: KtConstantExpression, data: Unit): Computation { + val bindingContext = trace.bindingContext + + val type: KotlinType = bindingContext.getType(expression) ?: return UNKNOWN_COMPUTATION + + val compileTimeConstant: CompileTimeConstant<*> + = bindingContext.get(BindingContext.COMPILE_TIME_VALUE, expression) ?: return UNKNOWN_COMPUTATION + val value: Any? = compileTimeConstant.getValue(type) + + return when (value) { + is Boolean -> value.lift() + null -> ESConstant.NULL + else -> UNKNOWN_COMPUTATION + } + } + + override fun visitIsExpression(expression: KtIsExpression, data: Unit): Computation { + val rightType: KotlinType = trace[BindingContext.TYPE, expression.typeReference] ?: return UNKNOWN_COMPUTATION + val arg = extractOrGetCached(expression.leftHandSide) + return CallComputation(DefaultBuiltIns.Instance.booleanType, IsFunctor(rightType, expression.isNegated).invokeWithArguments(listOf(arg))) + } + + override fun visitBinaryExpression(expression: KtBinaryExpression, data: Unit): Computation { + val left = extractOrGetCached(expression.left ?: return UNKNOWN_COMPUTATION) + val right = extractOrGetCached(expression.right ?: return UNKNOWN_COMPUTATION) + + val args = listOf(left, right) + + return when (expression.operationToken) { + KtTokens.EXCLEQ -> CallComputation(DefaultBuiltIns.Instance.booleanType, EqualsFunctor(true).invokeWithArguments(args)) + KtTokens.EQEQ -> CallComputation(DefaultBuiltIns.Instance.booleanType, EqualsFunctor(false).invokeWithArguments(args)) + KtTokens.ANDAND -> CallComputation(DefaultBuiltIns.Instance.booleanType, AndFunctor().invokeWithArguments(args)) + KtTokens.OROR -> CallComputation(DefaultBuiltIns.Instance.booleanType, OrFunctor().invokeWithArguments(args)) + else -> UNKNOWN_COMPUTATION + } + } + + override fun visitUnaryExpression(expression: KtUnaryExpression, data: Unit): Computation { + val arg = extractOrGetCached(expression.baseExpression ?: return UNKNOWN_COMPUTATION) + return when (expression.operationToken) { + KtTokens.EXCL -> CallComputation(DefaultBuiltIns.Instance.booleanType, NotFunctor().invokeWithArguments(arg)) + else -> UNKNOWN_COMPUTATION + } + } + + private fun ReceiverValue.toComputation(): Computation = when (this) { + is ExpressionReceiver -> extractOrGetCached(expression) + else -> UNKNOWN_COMPUTATION + } + + private fun KtExpression.createDataFlowValue(): DataFlowValue? { + return DataFlowValueFactory.createDataFlowValue( + expression = this, + type = trace.getType(this) ?: return null, + bindingContext = trace.bindingContext, + containingDeclarationOrModule = moduleDescriptor + ) + } + + private fun FunctionDescriptor.getFunctor(): Functor? { + trace[BindingContext.FUNCTOR, this]?.let { return it } + + val functor = ContractInterpretationDispatcher().resolveFunctor(this) ?: return null + trace.record(BindingContext.FUNCTOR, this, functor) + return functor + } + + private fun ResolvedCall<*>.isCallWithUnsupportedReceiver(): Boolean = + (extensionReceiver as? ExpressionReceiver)?.expression?.getResolvedCall(trace.bindingContext) == this || + (dispatchReceiver as? ExpressionReceiver)?.expression?.getResolvedCall(trace.bindingContext) == this || + (explicitReceiverKind == ExplicitReceiverKind.BOTH_RECEIVERS) + + private fun ResolvedCall<*>.getCallArgumentsAsComputations(): List? { + val arguments = mutableListOf() + arguments.addIfNotNull(extensionReceiver?.toComputation()) + arguments.addIfNotNull(dispatchReceiver?.toComputation()) + + valueArgumentsByIndex?.mapTo(arguments) { + val valueArgument = (it as? ExpressionValueArgument)?.valueArgument ?: return null + when (valueArgument) { + is KtLambdaArgument -> ESLambda(valueArgument.getLambdaExpression()) + else -> extractOrGetCached(valueArgument.getArgumentExpression() ?: return null) + } + } ?: return null + + return arguments + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/AbstractFilteringTrace.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/AbstractFilteringTrace.kt new file mode 100644 index 00000000000..72bbfdf2178 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/AbstractFilteringTrace.kt @@ -0,0 +1,42 @@ +/* + * 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.resolve + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.util.slicedMap.WritableSlice + +/** + * Trace which allows to keep some slices hidden from the parent trace. + * + * Compared with TemporaryBindingTrace + TraceEntryFilter, FilteringTrace doesn't + * make extra moves for slices that should be definitely recorded into parent + * (like storing them in the local map, later re-committing into parent's, etc.) + */ +abstract class AbstractFilteringTrace( + private val parentTrace: BindingTrace, + name: String +) : DelegatingBindingTrace(parentTrace.bindingContext, name, true, BindingTraceFilter.ACCEPT_ALL, false) { + abstract protected fun shouldBeHiddenFromParent(slice: WritableSlice, key: K): Boolean + + override fun record(slice: WritableSlice, key: K, value: V) { + if (shouldBeHiddenFromParent(slice, key)) super.record(slice, key, value) else parentTrace.record(slice, key, value) + } + + override fun report(diagnostic: Diagnostic) { + parentTrace.report(diagnostic) + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ConditionalDataFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ConditionalDataFlowInfo.kt new file mode 100644 index 00000000000..af0c522e3c4 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ConditionalDataFlowInfo.kt @@ -0,0 +1,35 @@ +/* + * 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.resolve.calls.smartcasts + +class ConditionalDataFlowInfo(val thenInfo: DataFlowInfo, val elseInfo: DataFlowInfo = thenInfo) { + fun and(other: ConditionalDataFlowInfo): ConditionalDataFlowInfo = when { + this == EMPTY -> other + other == EMPTY -> this + else -> ConditionalDataFlowInfo(this.thenInfo.and(other.thenInfo), this.elseInfo.and(other.elseInfo)) + } + + fun or(other: ConditionalDataFlowInfo): ConditionalDataFlowInfo = when { + this == EMPTY -> other + other == EMPTY -> this + else -> ConditionalDataFlowInfo(this.thenInfo.or(other.thenInfo), this.elseInfo.or(other.elseInfo)) + } + + companion object { + val EMPTY = ConditionalDataFlowInfo(DataFlowInfo.EMPTY) + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingComponents.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingComponents.java index d083d4a7f92..403f9737d34 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingComponents.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingComponents.java @@ -20,6 +20,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.context.GlobalContext; +import org.jetbrains.kotlin.contracts.EffectSystem; +import org.jetbrains.kotlin.contracts.parsing.ContractParsingServices; import org.jetbrains.kotlin.incremental.components.LookupTracker; import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap; import org.jetbrains.kotlin.resolve.*; @@ -64,6 +66,8 @@ public class ExpressionTypingComponents { /*package*/ WrappedTypeFactory wrappedTypeFactory; /*package*/ CollectionLiteralResolver collectionLiteralResolver; /*package*/ DeprecationResolver deprecationResolver; + /*package*/ EffectSystem effectSystem; + /*package*/ ContractParsingServices contractParsingServices; @Inject public void setGlobalContext(@NotNull GlobalContext globalContext) { @@ -181,7 +185,7 @@ public class ExpressionTypingComponents { } @Inject - public void setLocalVariableResolver(@NotNull LocalVariableResolver localVariableResolver) { + public void setLocalVariableResolver(@NotNull LocalVariableResolver localVariableResolver) { this.localVariableResolver = localVariableResolver; } @@ -219,4 +223,14 @@ public class ExpressionTypingComponents { public void setDeprecationResolver(DeprecationResolver deprecationResolver) { this.deprecationResolver = deprecationResolver; } + + @Inject + public void setEffectSystem(@NotNull EffectSystem effectSystem) { + this.effectSystem = effectSystem; + } + + @Inject + public void setContractParsingServices(@NotNull ContractParsingServices contractParsingServices) { + this.contractParsingServices = contractParsingServices; + } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java index 8ebe59281bd..3f47ebe313a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java @@ -40,6 +40,7 @@ import org.jetbrains.kotlin.resolve.scopes.TraceBasedLocalRedeclarationChecker; import org.jetbrains.kotlin.types.ErrorUtils; import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt; +import org.jetbrains.kotlin.util.slicedMap.WritableSlice; import java.util.Iterator; import java.util.List; @@ -260,7 +261,20 @@ public class ExpressionTypingServices { // Jump point data flow info DataFlowInfo beforeJumpInfo = newContext.dataFlowInfo; boolean jumpOutPossible = false; + + boolean isFirstStatement = true; for (Iterator iterator = block.iterator(); iterator.hasNext(); ) { + // Use filtering trace to keep effect system cache only for one statement + AbstractFilteringTrace traceForSingleStatement = new AbstractFilteringTrace(context.trace, "trace for single statement") { + @Override + protected boolean shouldBeHiddenFromParent(@NotNull WritableSlice slice, K key) { + return slice == BindingContext.EXPRESSION_EFFECTS; + } + }; + + newContext = newContext.replaceBindingTrace(traceForSingleStatement); + + KtElement statement = iterator.next(); if (!(statement instanceof KtExpression)) { continue; @@ -295,6 +309,12 @@ public class ExpressionTypingServices { // We take current data flow info if jump there is not possible } blockLevelVisitor = new ExpressionTypingVisitorDispatcher.ForBlock(expressionTypingComponents, annotationChecker, scope); + + expressionTypingComponents.contractParsingServices.checkContractAndRecordIfPresent(statementExpression, context.trace, scope, isFirstStatement); + + if (isFirstStatement) { + isFirstStatement = false; + } } return result.replaceJumpOutPossible(jumpOutPossible).replaceJumpFlowInfo(beforeJumpInfo); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt index 52bc3526153..7dee6a2167f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.calls.checkers.RttiExpressionInformation import org.jetbrains.kotlin.resolve.calls.checkers.RttiOperation import org.jetbrains.kotlin.resolve.calls.context.ContextDependency.INDEPENDENT +import org.jetbrains.kotlin.resolve.calls.smartcasts.ConditionalDataFlowInfo import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory @@ -379,8 +380,6 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping return newDataFlowInfo } - private class ConditionalDataFlowInfo(val thenInfo: DataFlowInfo, val elseInfo: DataFlowInfo = thenInfo) - private fun checkTypeForExpressionCondition( context: ExpressionTypingContext, expression: KtExpression,