Differ this/non-this instances for vars initialization in class
When deal with constructed object (not this) treat it like it's fully initialized. Otherwise (this or access with no receiver) access instruction should be handled as it was before. #KT-6788 Fixed #KT-4126 Fixed
This commit is contained in:
@@ -57,9 +57,7 @@ import org.jetbrains.kotlin.resolve.*;
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilPackage;
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver;
|
||||
import org.jetbrains.kotlin.types.JetType;
|
||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils;
|
||||
|
||||
@@ -324,19 +322,24 @@ public class JetFlowInformationProvider {
|
||||
new VariableInitContext(instruction, reportedDiagnosticMap, in, out, lexicalScopeVariableInfo);
|
||||
if (ctxt.variableDescriptor == null) return;
|
||||
if (instruction instanceof ReadValueInstruction) {
|
||||
JetElement element = ((ReadValueInstruction) instruction).getElement();
|
||||
ReadValueInstruction readValueInstruction = (ReadValueInstruction) instruction;
|
||||
JetElement element = readValueInstruction.getElement();
|
||||
boolean error = checkBackingField(ctxt, element);
|
||||
if (!error && declaredVariables.contains(ctxt.variableDescriptor)) {
|
||||
if (!error &&
|
||||
PseudocodeUtil.isThisOrNoDispatchReceiver(readValueInstruction, trace.getBindingContext()) &&
|
||||
declaredVariables.contains(ctxt.variableDescriptor)) {
|
||||
checkIsInitialized(ctxt, element, varWithUninitializedErrorGenerated);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!(instruction instanceof WriteValueInstruction)) return;
|
||||
JetElement element = ((WriteValueInstruction) instruction).getlValue();
|
||||
WriteValueInstruction writeValueInstruction = (WriteValueInstruction) instruction;
|
||||
JetElement element = writeValueInstruction.getlValue();
|
||||
boolean error = checkBackingField(ctxt, element);
|
||||
if (!(element instanceof JetExpression)) return;
|
||||
if (!error) {
|
||||
error = checkValReassignment(ctxt, (JetExpression) element, varWithValReassignErrorGenerated);
|
||||
error = checkValReassignment(ctxt, (JetExpression) element, writeValueInstruction,
|
||||
varWithValReassignErrorGenerated);
|
||||
}
|
||||
if (!error && processClassOrObject) {
|
||||
error = checkAssignmentBeforeDeclaration(ctxt, (JetExpression) element);
|
||||
@@ -367,6 +370,7 @@ public class JetFlowInformationProvider {
|
||||
) {
|
||||
if (!(element instanceof JetSimpleNameExpression)) return;
|
||||
|
||||
|
||||
boolean isInitialized = ctxt.exitInitState.isInitialized;
|
||||
VariableDescriptor variableDescriptor = ctxt.variableDescriptor;
|
||||
if (variableDescriptor instanceof PropertyDescriptor) {
|
||||
@@ -391,6 +395,7 @@ public class JetFlowInformationProvider {
|
||||
private boolean checkValReassignment(
|
||||
@NotNull VariableInitContext ctxt,
|
||||
@NotNull JetExpression expression,
|
||||
@NotNull WriteValueInstruction writeValueInstruction,
|
||||
@NotNull Collection<VariableDescriptor> varWithValReassignErrorGenerated
|
||||
) {
|
||||
VariableDescriptor variableDescriptor = ctxt.variableDescriptor;
|
||||
@@ -429,8 +434,9 @@ public class JetFlowInformationProvider {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((isInitializedNotHere || !hasBackingField) && !variableDescriptor.isVar()
|
||||
&& !varWithValReassignErrorGenerated.contains(variableDescriptor)) {
|
||||
boolean isThisOrNoDispatchReceiver =
|
||||
PseudocodeUtil.isThisOrNoDispatchReceiver(writeValueInstruction, trace.getBindingContext());
|
||||
if ((isInitializedNotHere || !hasBackingField || !isThisOrNoDispatchReceiver) && !variableDescriptor.isVar()) {
|
||||
boolean hasReassignMethodReturningUnit = false;
|
||||
JetSimpleNameExpression operationReference = null;
|
||||
PsiElement parent = expression.getParent();
|
||||
@@ -460,8 +466,14 @@ public class JetFlowInformationProvider {
|
||||
}
|
||||
}
|
||||
if (!hasReassignMethodReturningUnit) {
|
||||
varWithValReassignErrorGenerated.add(variableDescriptor);
|
||||
report(Errors.VAL_REASSIGNMENT.on(expression, variableDescriptor), ctxt);
|
||||
if (!isThisOrNoDispatchReceiver || !varWithValReassignErrorGenerated.contains(variableDescriptor)) {
|
||||
report(Errors.VAL_REASSIGNMENT.on(expression, variableDescriptor), ctxt);
|
||||
}
|
||||
if (isThisOrNoDispatchReceiver) {
|
||||
// try to get rid of repeating VAL_REASSIGNMENT diagnostic only for vars with no receiver
|
||||
// or when receiver is this
|
||||
varWithValReassignErrorGenerated.add(variableDescriptor);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -811,7 +823,14 @@ public class JetFlowInformationProvider {
|
||||
new TailRecursionDetector(subroutine, callInstruction)
|
||||
);
|
||||
|
||||
boolean sameDispatchReceiver = sameDispatchReceiver(resolvedCall);
|
||||
// A tail call is not allowed to change dispatch receiver
|
||||
// class C {
|
||||
// fun foo(other: C) {
|
||||
// other.foo(this) // not a tail call
|
||||
// }
|
||||
// }
|
||||
boolean sameDispatchReceiver =
|
||||
PseudocodeUtil.isThisOrNoDispatchReceiver(resolvedCall, trace.getBindingContext());
|
||||
|
||||
TailRecursionKind kind = isTail && sameDispatchReceiver ? TAIL_CALL : NON_TAIL;
|
||||
|
||||
@@ -848,33 +867,6 @@ public class JetFlowInformationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sameDispatchReceiver(ResolvedCall<?> resolvedCall) {
|
||||
// A tail call is not allowed to change dispatch receiver
|
||||
// class C {
|
||||
// fun foo(other: C) {
|
||||
// other.foo(this) // not a tail call
|
||||
// }
|
||||
// }
|
||||
ReceiverParameterDescriptor dispatchReceiverParameter = resolvedCall.getResultingDescriptor().getDispatchReceiverParameter();
|
||||
ReceiverValue dispatchReceiverValue = resolvedCall.getDispatchReceiver();
|
||||
if (dispatchReceiverParameter == null || !dispatchReceiverValue.exists()) return true;
|
||||
|
||||
DeclarationDescriptor classDescriptor = null;
|
||||
if (dispatchReceiverValue instanceof ThisReceiver) {
|
||||
// foo() -- implicit receiver
|
||||
classDescriptor = ((ThisReceiver) dispatchReceiverValue).getDeclarationDescriptor();
|
||||
}
|
||||
else if (dispatchReceiverValue instanceof ExpressionReceiver) {
|
||||
JetExpression expression = JetPsiUtil.deparenthesize(((ExpressionReceiver) dispatchReceiverValue).getExpression());
|
||||
if (expression instanceof JetThisExpression) {
|
||||
// this.foo() -- explicit receiver
|
||||
JetThisExpression thisExpression = (JetThisExpression) expression;
|
||||
classDescriptor = trace.get(BindingContext.REFERENCE_TARGET, thisExpression.getInstanceReference());
|
||||
}
|
||||
}
|
||||
return dispatchReceiverParameter.getContainingDeclaration() == classDescriptor;
|
||||
}
|
||||
|
||||
private static TailRecursionKind combineKinds(TailRecursionKind kind, @Nullable TailRecursionKind existingKind) {
|
||||
TailRecursionKind resultingKind;
|
||||
if (existingKind == null || existingKind == kind) {
|
||||
|
||||
@@ -202,6 +202,11 @@ public class PseudocodeVariablesData {
|
||||
}
|
||||
Map<VariableDescriptor, VariableInitState> exitInstructionData = Maps.newHashMap(enterInstructionData);
|
||||
if (instruction instanceof WriteValueInstruction) {
|
||||
// if writing to already initialized object
|
||||
if (!PseudocodeUtil.isThisOrNoDispatchReceiver((WriteValueInstruction) instruction, bindingContext)) {
|
||||
return enterInstructionData;
|
||||
}
|
||||
|
||||
VariableInitState enterInitState = enterInstructionData.get(variable);
|
||||
VariableInitState initializationAtThisElement =
|
||||
VariableInitState.create(((WriteValueInstruction) instruction).getElement() instanceof JetProperty, enterInitState);
|
||||
|
||||
@@ -20,16 +20,20 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.cfg.JetControlFlowProcessor;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.ReadValueInstruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.WriteValueInstruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.VariableDeclarationInstruction;
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor;
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
||||
import org.jetbrains.kotlin.psi.JetDeclaration;
|
||||
import org.jetbrains.kotlin.psi.JetElement;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.BindingContextUtils;
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver;
|
||||
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice;
|
||||
import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
|
||||
|
||||
@@ -85,4 +89,53 @@ public class PseudocodeUtil {
|
||||
}
|
||||
return BindingContextUtils.extractVariableDescriptorIfAny(bindingContext, element, onlyReference);
|
||||
}
|
||||
|
||||
// When deal with constructed object (not this) treat it like it's fully initialized
|
||||
// Otherwise (this or access with empty receiver) access instruction should be handled as usual
|
||||
public static boolean isThisOrNoDispatchReceiver(
|
||||
@NotNull AccessValueInstruction instruction,
|
||||
@NotNull BindingContext bindingContext
|
||||
) {
|
||||
if (instruction.getReceiverValues().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
AccessTarget accessTarget = instruction.getTarget();
|
||||
if (accessTarget instanceof AccessTarget.BlackBox) return false;
|
||||
assert accessTarget instanceof AccessTarget.Call :
|
||||
"AccessTarget.Declaration has no receivers and it's not BlackBox, so it should be Call";
|
||||
|
||||
ResolvedCall<?> accessResolvedCall = ((AccessTarget.Call) accessTarget).getResolvedCall();
|
||||
return isThisOrNoDispatchReceiver(accessResolvedCall, bindingContext);
|
||||
}
|
||||
|
||||
public static boolean isThisOrNoDispatchReceiver(
|
||||
@NotNull ResolvedCall<?> resolvedCall,
|
||||
@NotNull BindingContext bindingContext
|
||||
) {
|
||||
// it returns true if call has no dispatch receiver (e.g. resulting descriptor is top-level function or local variable)
|
||||
// or call receiver is effectively `this` instance (explicitly or implicitly) of resulting descriptor
|
||||
// class A(other: A) {
|
||||
// val x
|
||||
// val y = other.x // return false for `other.x` as it's receiver is not `this`
|
||||
// }
|
||||
ReceiverParameterDescriptor dispatchReceiverParameter = resolvedCall.getResultingDescriptor().getDispatchReceiverParameter();
|
||||
ReceiverValue dispatchReceiverValue = resolvedCall.getDispatchReceiver();
|
||||
if (dispatchReceiverParameter == null || !dispatchReceiverValue.exists()) return true;
|
||||
|
||||
DeclarationDescriptor classDescriptor = null;
|
||||
if (dispatchReceiverValue instanceof ThisReceiver) {
|
||||
// foo() -- implicit receiver
|
||||
classDescriptor = ((ThisReceiver) dispatchReceiverValue).getDeclarationDescriptor();
|
||||
}
|
||||
else if (dispatchReceiverValue instanceof ExpressionReceiver) {
|
||||
JetExpression expression = JetPsiUtil.deparenthesize(((ExpressionReceiver) dispatchReceiverValue).getExpression());
|
||||
if (expression instanceof JetThisExpression) {
|
||||
// this.foo() -- explicit receiver
|
||||
JetThisExpression thisExpression = (JetThisExpression) expression;
|
||||
classDescriptor = bindingContext.get(BindingContext.REFERENCE_TARGET, thisExpression.getInstanceReference());
|
||||
}
|
||||
}
|
||||
return dispatchReceiverParameter.getContainingDeclaration() == classDescriptor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user