[IR/FE] BindingContext: Change USED_AS_EXPRESSION to Basic Slice

This commit changes the slice implementation used for
`USED_AS_EXPRESSION`.

Here is the problem as identified and mitigated by this commit:

Slices are key-value maps. They are not total over the domain of keys,
so "looking up" a key not in the slice is a valid operation, yielding
`null`.

A binding context is a collection of slices.

Binding contexts can be stacked as composite binding contexts to
e.g. facilitate scoped analyses. Looking up a key proceeds down the
stack, yielding the first non-null value, or `null` if no slice in the
stack of binding contexts contain that key.

A slice made by `createSimpleSetSlice` models a "set" of keys that can
be enlarged by inclusion: adding `(k, true)` to the slice indicates
`k` belongs to the set. `(k, false)` indicates non-membership.

However, looking up a key _not in_ the slice yields _false_, rather
than null.

Hence, simple set slices do not compose in composite binding traces.

This was encountered porting the expression evaluator to the IR
backend. PSI2IR uses `USED_AS_EXPRESSION` to generate expression body
functions properly. The frontend analysis of the fragment is layered
ontop of the binding context from the editor's analysis of the
underlying project being debugged, which contains speficially the
analysis results of inline functions called from the fragment.
This commit is contained in:
Kristoffer Andersen
2021-11-23 17:56:14 +01:00
committed by Alexander Udalov
parent a70c336c2c
commit 39fee12f32
4 changed files with 23 additions and 7 deletions
@@ -51,6 +51,7 @@ import java.util.Collections;
import static org.jetbrains.kotlin.util.slicedMap.RewritePolicy.DO_NOTHING;
import static org.jetbrains.kotlin.util.slicedMap.Slices.COMPILE_TIME_VALUE_REWRITE_POLICY;
import static org.jetbrains.kotlin.util.slicedMap.Slices.CONSERVATIVE_SET_INCLUSION_SEMANTICS;
public interface BindingContext {
BindingContext EMPTY = new BindingContext() {
@@ -179,7 +180,7 @@ public interface BindingContext {
WritableSlice<KtExpression, Boolean> PROCESSED = Slices.createSimpleSlice();
// Please do not use this slice (USED_AS_EXPRESSION) directly,
// use extension element.isUsedAsExpression() instead
WritableSlice<KtElement, Boolean> USED_AS_EXPRESSION = Slices.createSimpleSetSlice();
WritableSlice<KtElement, Boolean> USED_AS_EXPRESSION = new BasicWritableSlice<>(CONSERVATIVE_SET_INCLUSION_SEMANTICS);
WritableSlice<KtElement, Boolean> USED_AS_RESULT_OF_LAMBDA = Slices.createSimpleSetSlice();
WritableSlice<VariableDescriptor, CaptureKind> CAPTURED_IN_CLOSURE = new BasicWritableSlice<>(DO_NOTHING);
@@ -57,11 +57,7 @@ fun KtReturnExpression.getTargetFunction(context: BindingContext): KtCallableDec
}
fun KtExpression.isUsedAsExpression(context: BindingContext): Boolean =
context[USED_AS_EXPRESSION, this]
?: throw AssertionError(
"BindingContext returned null for Boolean slice: " +
if (context == EMPTY) "BindingContext.EMPTY" else context.javaClass.toString()
)
context[USED_AS_EXPRESSION, this] ?: false
fun KtExpression.isUsedAsResultOfLambda(context: BindingContext): Boolean = context[USED_AS_RESULT_OF_LAMBDA, this]!!
fun KtExpression.isUsedAsStatement(context: BindingContext): Boolean = !isUsedAsExpression(context)
@@ -80,6 +80,24 @@ public class Slices {
}
};
// For Boolean-valued slices modelling set-membership, this policy
// implements constructing a set by element-wise inclusion, conservatively:
// allow rewriting false->true but not true->false.
//
// Used in growing the set of `USED_AS_EXPRESSION` in CFG analysis.
public static final RewritePolicy CONSERVATIVE_SET_INCLUSION_SEMANTICS = new RewritePolicy() {
@Override
public <K> boolean rewriteProcessingNeeded(K key) {
return true;
}
@Override
public <K, V> boolean processRewrite(WritableSlice<K, V> slice, K key, V oldValue, V newValue) {
assert (newValue instanceof Boolean);
return (Boolean)newValue;
}
};
private Slices() {
}
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffsetSkippingComments
import org.jetbrains.kotlin.psi2ir.deparenthesize
import org.jetbrains.kotlin.psi2ir.intermediate.loadAt
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.utils.SmartList
class BranchingExpressionGenerator(statementGenerator: StatementGenerator) : StatementGeneratorExtension(statementGenerator) {
@@ -142,7 +143,7 @@ class BranchingExpressionGenerator(statementGenerator: StatementGenerator) : Sta
}
private fun addElseBranchForExhaustiveWhenIfNeeded(irWhen: IrWhen, whenExpression: KtWhenExpression) {
val isUsedAsExpression = true == get(BindingContext.USED_AS_EXPRESSION, whenExpression)
val isUsedAsExpression = whenExpression.isUsedAsExpression(context.bindingContext)
val isImplicitElseRequired =
if (isUsedAsExpression)
true == get(BindingContext.EXHAUSTIVE_WHEN, whenExpression)