From d502d457727994426fe3944848415b4df53a97ea Mon Sep 17 00:00:00 2001 From: Evgeny Gerashchenko Date: Wed, 10 Apr 2013 15:41:04 +0400 Subject: [PATCH] Disabled highlighting for variables which are captured in inlined closure. --- .../jet/codegen/binding/CodegenBinding.java | 3 +- .../lang/cfg/JetFlowInformationProvider.java | 2 +- .../jet/lang/resolve/BindingContext.java | 5 +- .../lang/types/expressions/CaptureKind.java | 22 +++++ .../expressions/ExpressionTypingUtils.java | 70 ++++++++++++-- .../VariablesHighlightingVisitor.java | 3 +- .../checker/infos/CapturedInInlinedClosure.kt | 91 +++++++++++++++++++ .../checkers/JetPsiCheckerTestGenerated.java | 5 + 8 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/CaptureKind.java create mode 100644 idea/testData/checker/infos/CapturedInInlinedClosure.kt diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenBinding.java b/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenBinding.java index a9a830f2d02..f30b166d06e 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenBinding.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenBinding.java @@ -372,8 +372,7 @@ public class CodegenBinding { public static boolean isVarCapturedInClosure(BindingContext bindingContext, DeclarationDescriptor descriptor) { if (!(descriptor instanceof VariableDescriptor) || descriptor instanceof PropertyDescriptor) return false; VariableDescriptor variableDescriptor = (VariableDescriptor) descriptor; - return Boolean.TRUE.equals(bindingContext.get(CAPTURED_IN_CLOSURE, variableDescriptor)) && - variableDescriptor.isVar(); + return bindingContext.get(CAPTURED_IN_CLOSURE, variableDescriptor) != null && variableDescriptor.isVar(); } public static boolean hasThis0(BindingContext bindingContext, ClassDescriptor classDescriptor) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java index 3e0d6ef31c1..65587bff315 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java @@ -462,7 +462,7 @@ public class JetFlowInformationProvider { !DescriptorUtils.isLocal(variableDescriptor.getContainingDeclaration(), variableDescriptor)) return; PseudocodeVariablesData.VariableUseState variableUseState = in.get(variableDescriptor); if (instruction instanceof WriteValueInstruction) { - if (trace.get(CAPTURED_IN_CLOSURE, variableDescriptor)) return; + if (trace.get(CAPTURED_IN_CLOSURE, variableDescriptor) != null) return; JetElement element = ((WriteValueInstruction) instruction).getElement(); if (variableUseState != LAST_READ) { if (element instanceof JetBinaryExpression && diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java index 1f821036d34..6f5a275b83e 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java @@ -26,14 +26,13 @@ import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor; import org.jetbrains.jet.lang.diagnostics.Diagnostic; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo; -import org.jetbrains.jet.lang.resolve.calls.context.CallCandidateResolutionContext; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; -import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResultsImpl; import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant; import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.resolve.scopes.JetScope; import org.jetbrains.jet.lang.types.DeferredType; import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.expressions.CaptureKind; import org.jetbrains.jet.util.Box; import org.jetbrains.jet.util.slicedmap.*; @@ -126,7 +125,7 @@ public interface BindingContext { WritableSlice PROCESSED = Slices.createSimpleSetSlice(); WritableSlice STATEMENT = Slices.createRemovableSetSlice(); - WritableSlice CAPTURED_IN_CLOSURE = Slices.createSimpleSetSlice(); + WritableSlice CAPTURED_IN_CLOSURE = new BasicWritableSlice(DO_NOTHING); // enum DeferredTypeKey {DEFERRED_TYPE_KEY} // WritableSlice> DEFERRED_TYPES = Slices.createSimpleSlice(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/CaptureKind.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/CaptureKind.java new file mode 100644 index 00000000000..0784f016bce --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/CaptureKind.java @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2013 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.jet.lang.types.expressions; + +public enum CaptureKind { + NOT_INLINE, + INLINE_ONLY +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingUtils.java index cd1ab8703e1..8cabe24a372 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingUtils.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingUtils.java @@ -33,14 +33,15 @@ import org.jetbrains.jet.lang.diagnostics.Diagnostic; import org.jetbrains.jet.lang.diagnostics.Errors; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.*; -import org.jetbrains.jet.lang.resolve.calls.context.ExpressionPosition; -import org.jetbrains.jet.lang.resolve.calls.util.CallMaker; -import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResults; import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo; +import org.jetbrains.jet.lang.resolve.calls.context.ExpressionPosition; import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintPosition; import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintSystem; import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintSystemImpl; import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintsUtil; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; +import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResults; +import org.jetbrains.jet.lang.resolve.calls.util.CallMaker; import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.lang.resolve.scopes.JetScope; @@ -139,12 +140,69 @@ public class ExpressionTypingUtils { ).contains(expression.getNode().getElementType()); } + private static boolean isCapturedInInline( + @NotNull BindingContext context, + @NotNull DeclarationDescriptor scopeContainer, + @NotNull DeclarationDescriptor variableParent + ) { + PsiElement scopeDeclaration = BindingContextUtils.descriptorToDeclaration(context, scopeContainer); + if (!(scopeDeclaration instanceof JetFunctionLiteral)) { + return false; + } + + PsiElement parent = scopeDeclaration.getParent(); + assert parent instanceof JetFunctionLiteralExpression : "parent of JetFunctionLiteral is " + parent; + JetCallExpression callExpression = getCallExpression((JetFunctionLiteralExpression) parent); + if (callExpression == null) { + return false; + } + + ResolvedCall call = context.get(BindingContext.RESOLVED_CALL, callExpression.getCalleeExpression()); + if (call == null) { + return false; + } + + CallableDescriptor callable = call.getResultingDescriptor(); + if (callable instanceof SimpleFunctionDescriptor && ((SimpleFunctionDescriptor) callable).isInline()) { + DeclarationDescriptor scopeContainerParent = scopeContainer.getContainingDeclaration(); + assert scopeContainerParent != null : "parent is null for " + scopeContainer; + return scopeContainerParent == variableParent || isCapturedInInline(context, scopeContainerParent, variableParent); + } + else { + return false; + } + } + + @Nullable + private static JetCallExpression getCallExpression(@NotNull JetFunctionLiteralExpression functionLiteralExpression) { + PsiElement parent = functionLiteralExpression.getParent(); + if (parent instanceof JetValueArgument) { + // foo({ ... }) or foo(f = { ... }) + + PsiElement valueArgumentList = parent.getParent(); + assert valueArgumentList instanceof JetValueArgumentList : "parent of value argument is " + valueArgumentList; + + if (valueArgumentList.getParent() instanceof JetCallExpression) { // may be argument list of annotation + return (JetCallExpression) valueArgumentList.getParent(); + } + } + else if (parent instanceof JetCallExpression) { + // foo { ... } + + return (JetCallExpression) parent; + } + return null; + } public static void checkCapturingInClosure(JetSimpleNameExpression expression, BindingTrace trace, JetScope scope) { VariableDescriptor variable = BindingContextUtils.extractVariableDescriptorIfAny(trace.getBindingContext(), expression, true); if (variable != null) { - DeclarationDescriptor containingDeclaration = variable.getContainingDeclaration(); - if (scope.getContainingDeclaration() != containingDeclaration && containingDeclaration instanceof CallableDescriptor) { - trace.record(CAPTURED_IN_CLOSURE, variable); + DeclarationDescriptor variableParent = variable.getContainingDeclaration(); + DeclarationDescriptor scopeContainer = scope.getContainingDeclaration(); + if (scopeContainer != variableParent && variableParent instanceof CallableDescriptor) { + if (trace.get(CAPTURED_IN_CLOSURE, variable) != CaptureKind.NOT_INLINE) { + boolean inline = isCapturedInInline(trace.getBindingContext(), scopeContainer, variableParent); + trace.record(CAPTURED_IN_CLOSURE, variable, inline ? CaptureKind.INLINE_ONLY : CaptureKind.NOT_INLINE); + } } } } diff --git a/idea/src/org/jetbrains/jet/plugin/highlighter/VariablesHighlightingVisitor.java b/idea/src/org/jetbrains/jet/plugin/highlighter/VariablesHighlightingVisitor.java index 10d7fd5d77f..28cf2c3d1c6 100644 --- a/idea/src/org/jetbrains/jet/plugin/highlighter/VariablesHighlightingVisitor.java +++ b/idea/src/org/jetbrains/jet/plugin/highlighter/VariablesHighlightingVisitor.java @@ -24,6 +24,7 @@ import org.jetbrains.jet.lang.descriptors.impl.LocalVariableDescriptor; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.expressions.CaptureKind; import org.jetbrains.jet.renderer.DescriptorRenderer; import static org.jetbrains.jet.lang.resolve.BindingContext.*; @@ -88,7 +89,7 @@ class VariablesHighlightingVisitor extends AfterAnalysisHighlightingVisitor { JetPsiChecker.highlightName(holder, elementToHighlight, JetHighlightingColors.MUTABLE_VARIABLE); } - if (Boolean.TRUE.equals(bindingContext.get(CAPTURED_IN_CLOSURE, variableDescriptor))) { + if (bindingContext.get(CAPTURED_IN_CLOSURE, variableDescriptor) == CaptureKind.NOT_INLINE) { String msg = ((VariableDescriptor) descriptor).isVar() ? "Wrapped into a reference object to be modified when captured in a closure" : "Value captured in a closure"; diff --git a/idea/testData/checker/infos/CapturedInInlinedClosure.kt b/idea/testData/checker/infos/CapturedInInlinedClosure.kt new file mode 100644 index 00000000000..ce3c54afac4 --- /dev/null +++ b/idea/testData/checker/infos/CapturedInInlinedClosure.kt @@ -0,0 +1,91 @@ +inline fun run(f: () -> T) = f() +fun run2(f: () -> Unit) = f() + +fun inline() { + val x = 1 + run { x } + + val x1 = 1 + run ({ x1 }) + + val x2 = 1 + run (f = { x2 }) + + val x3 = 1 + run { + run { + x3 + } + } +} + +fun notInline() { + val y2 = 1 + run { y2 } + run2 { y2 } + + val y3 = 1 + run2 { y3 } + run { y3 } + + // wrapped, using in not inline + val z = 2 + { z }() + + val z1 = 3 + run2 { z1 } +} + +fun nestedDifferent() { // inline within non-inline and vice-versa + val y = 1 + { + run { + y + } + }() + + val y1 = 1 + run { + { y1 }() + } +} + +fun localFunctionAndClass() { + val u = 1 + fun localFun() { + run { + u + } + } + + val v = 1 // erroneous "unused warning" is caused by KT-3501 + class LocalClass { + fun f() { + run { + v + } + } + } +} + +fun objectExpression() { + val u1 = 1 + object : Any() { + fun f() { + run { + u1 + } + } + } + + val u2 = 1 + object : Any() { + val prop = run { + u2 + } + } + + val u3 = "" + object : Throwable(run { u3 }) { + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/checkers/JetPsiCheckerTestGenerated.java b/idea/tests/org/jetbrains/jet/checkers/JetPsiCheckerTestGenerated.java index 6479d073a74..306bf099898 100644 --- a/idea/tests/org/jetbrains/jet/checkers/JetPsiCheckerTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/checkers/JetPsiCheckerTestGenerated.java @@ -417,6 +417,11 @@ public class JetPsiCheckerTestGenerated extends AbstractJetPsiCheckerTest { doTestWithInfos("idea/testData/checker/infos/Autocasts.kt"); } + @TestMetadata("CapturedInInlinedClosure.kt") + public void testCapturedInInlinedClosure() throws Exception { + doTestWithInfos("idea/testData/checker/infos/CapturedInInlinedClosure.kt"); + } + @TestMetadata("PropertiesWithBackingFields.kt") public void testPropertiesWithBackingFields() throws Exception { doTestWithInfos("idea/testData/checker/infos/PropertiesWithBackingFields.kt");