KT-456 No check for obligatory return in getters

This commit is contained in:
svtk
2011-11-09 12:22:08 +04:00
parent 24d663a08f
commit 2d6337fe93
12 changed files with 130 additions and 145 deletions
@@ -548,7 +548,7 @@ public class JetControlFlowProcessor {
if (subroutine instanceof JetFunctionLiteralExpression) {
subroutine = ((JetFunctionLiteralExpression) subroutine).getFunctionLiteral();
}
if (subroutine instanceof JetFunction) {
if (subroutine instanceof JetFunction || subroutine instanceof JetPropertyAccessor || subroutine instanceof JetConstructor) {
if (returnedExpression == null) {
builder.returnNoValue(expression, subroutine);
}
@@ -560,6 +560,9 @@ public class JetControlFlowProcessor {
if (labelElement != null) {
trace.report(NOT_A_RETURN_LABEL.on(expression, expression.getLabelName()));
}
else {
trace.report(RETURN_NOT_ALLOWED.on(expression));
}
}
}
@@ -16,10 +16,14 @@ import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingContextUtils;
import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.types.JetStandardClasses;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lexer.JetTokens;
import java.util.*;
import static org.jetbrains.jet.lang.diagnostics.Errors.*;
import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE;
/**
* @author svtk
*/
@@ -67,7 +71,7 @@ public class JetFlowInformationProvider {
wrappedTrace.close();
}
public void collectReturnExpressions(@NotNull JetElement subroutine, @NotNull final Collection<JetExpression> returnedExpressions) {
private void collectReturnExpressions(@NotNull JetElement subroutine, @NotNull final Collection<JetExpression> returnedExpressions) {
Pseudocode pseudocode = pseudocodeMap.get(subroutine);
assert pseudocode != null;
@@ -119,20 +123,65 @@ public class JetFlowInformationProvider {
});
}
}
public void checkDefiniteReturn(@NotNull JetDeclarationWithBody function, final @NotNull JetType expectedReturnType) {
assert function instanceof JetDeclaration;
public void collectUnreachableExpressions(@NotNull JetElement subroutine, @NotNull Collection<JetElement> unreachableElements) {
JetExpression bodyExpression = function.getBodyExpression();
if (bodyExpression == null) return;
List<JetExpression> returnedExpressions = Lists.newArrayList();
collectReturnExpressions(function.asElement(), returnedExpressions);
boolean nothingReturned = returnedExpressions.isEmpty();
returnedExpressions.remove(function); // This will be the only "expression" if the body is empty
if (expectedReturnType != NO_EXPECTED_TYPE && !JetStandardClasses.isUnit(expectedReturnType) && returnedExpressions.isEmpty() && !nothingReturned) {
trace.report(RETURN_TYPE_MISMATCH.on(bodyExpression, expectedReturnType));
}
final boolean blockBody = function.hasBlockBody();
final Set<JetElement> rootUnreachableElements = collectUnreachableCode(function.asElement());
for (JetElement element : rootUnreachableElements) {
trace.report(UNREACHABLE_CODE.on(element));
}
for (JetExpression returnedExpression : returnedExpressions) {
returnedExpression.accept(new JetVisitorVoid() {
@Override
public void visitReturnExpression(JetReturnExpression expression) {
if (!blockBody) {
trace.report(RETURN_IN_FUNCTION_WITH_EXPRESSION_BODY.on(expression));
}
}
@Override
public void visitExpression(JetExpression expression) {
if (blockBody && expectedReturnType != NO_EXPECTED_TYPE && !JetStandardClasses.isUnit(expectedReturnType) && !rootUnreachableElements.contains(expression)) {
trace.report(NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY.on(expression));
}
}
});
}
}
private Set<JetElement> collectUnreachableCode(@NotNull JetElement subroutine) {
Pseudocode pseudocode = pseudocodeMap.get(subroutine);
assert pseudocode != null;
Collection<JetElement> unreachableElements = Lists.newArrayList();
for (Instruction deadInstruction : pseudocode.getDeadInstructions()) {
if (deadInstruction instanceof JetElementInstruction &&
!(deadInstruction instanceof ReadUnitValueInstruction)) {
unreachableElements.add(((JetElementInstruction) deadInstruction).getElement());
}
}
// This is needed in order to highlight only '1 < 2' and not '1', '<' and '2' as well
return JetPsiUtil.findRootExpressions(unreachableElements);
}
public void markUninitializedVariables(@NotNull JetElement subroutine, final boolean inAnonymousInitializers, final boolean analyzeLocalDeclaration) {
public void markUninitializedVariables(@NotNull JetElement subroutine, final boolean inAnonymousInitializers, final boolean inLocalDeclaration) {
final Pseudocode pseudocode = pseudocodeMap.get(subroutine);
assert pseudocode != null;
@@ -202,7 +251,7 @@ public class JetFlowInformationProvider {
isInitialized = true;
}
}
if (!analyzeLocalDeclaration && !isInitialized && !varWithUninitializedErrorGenerated.contains(variableDescriptor)) {
if (!inLocalDeclaration && !isInitialized && !varWithUninitializedErrorGenerated.contains(variableDescriptor)) {
varWithUninitializedErrorGenerated.add(variableDescriptor);
trace.report(Errors.UNINITIALIZED_VARIABLE.on((JetSimpleNameExpression) element, variableDescriptor));
}
@@ -225,7 +274,7 @@ public class JetFlowInformationProvider {
}
}
JetExpression expression = (JetExpression) element;
if (!analyzeLocalDeclaration && hasInitializer && !variableDescriptor.isVar() && !varWithValReassignErrorGenerated.contains(variableDescriptor)) {
if (!inLocalDeclaration && hasInitializer && !variableDescriptor.isVar() && !varWithValReassignErrorGenerated.contains(variableDescriptor)) {
PsiElement psiElement = trace.get(BindingContext.DESCRIPTOR_TO_DECLARATION, variableDescriptor);
JetProperty property = psiElement instanceof JetProperty ? (JetProperty) psiElement : null;
varWithValReassignErrorGenerated.add(variableDescriptor);
@@ -254,7 +303,7 @@ public class JetFlowInformationProvider {
for (Instruction instruction : pseudocode.getInstructions()) {
if (instruction instanceof LocalDeclarationInstruction) {
JetElement element = ((LocalDeclarationInstruction) instruction).getElement();
markUninitializedVariables(element, false, analyzeLocalDeclaration);
markUninitializedVariables(element, false, inLocalDeclaration);
}
}
}
@@ -45,12 +45,7 @@ public class JetControlFlowInstructionsGenerator extends JetControlFlowBuilderAd
@Override
public void enterSubroutine(@NotNull JetDeclaration subroutine) {
if (builder != null) {
pushBuilder(subroutine, builder.getCurrentSubroutine());
}
else {
pushBuilder(subroutine, subroutine);
}
pushBuilder(subroutine, subroutine);
assert builder != null;
builder.enterSubroutine(subroutine);
}
@@ -281,11 +281,11 @@ public class BodyResolver {
}
private void resolveSecondaryConstructorBodies() {
for (Map.Entry<JetDeclaration, ConstructorDescriptor> entry : this.context.getConstructors().entrySet()) {
JetDeclaration declaration = entry.getKey();
for (Map.Entry<JetConstructor, ConstructorDescriptor> entry : this.context.getConstructors().entrySet()) {
JetConstructor constructor = entry.getKey();
ConstructorDescriptor descriptor = entry.getValue();
resolveSecondaryConstructorBody((JetConstructor) declaration, descriptor, ((MutableClassDescriptor) descriptor.getContainingDeclaration()).getScopeForMemberResolution());
resolveSecondaryConstructorBody(constructor, descriptor, ((MutableClassDescriptor) descriptor.getContainingDeclaration()).getScopeForMemberResolution());
assert descriptor.getReturnType() != null;
}
@@ -1,22 +1,16 @@
package org.jetbrains.jet.lang.resolve;
import com.google.common.collect.Lists;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.cfg.JetFlowInformationProvider;
import org.jetbrains.jet.lang.cfg.pseudocode.JetControlFlowDataTraceFactory;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.scopes.JetScope;
import org.jetbrains.jet.lang.types.JetStandardClasses;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.expressions.ExpressionTypingServices;
import org.jetbrains.jet.lexer.JetTokens;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.jetbrains.jet.lang.diagnostics.Errors.*;
import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE;
/**
@@ -24,130 +18,70 @@ import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE;
*/
public class ControlFlowAnalyzer {
private TopDownAnalysisContext context;
private ExpressionTypingServices typeInferrerServices;
private final JetControlFlowDataTraceFactory flowDataTraceFactory;
private final boolean analyzeLocalDeclaration;
private final boolean inLocalDeclaration;
public ControlFlowAnalyzer(TopDownAnalysisContext context, JetControlFlowDataTraceFactory flowDataTraceFactory, boolean analyzeLocalDeclaration) {
public ControlFlowAnalyzer(TopDownAnalysisContext context, JetControlFlowDataTraceFactory flowDataTraceFactory, boolean inLocalDeclaration) {
this.context = context;
this.flowDataTraceFactory = flowDataTraceFactory;
this.analyzeLocalDeclaration = analyzeLocalDeclaration;
this.typeInferrerServices = context.getSemanticServices().getTypeInferrerServices(context.getTrace());
this.inLocalDeclaration = inLocalDeclaration;
}
public void process() {
for (Map.Entry<JetClass, MutableClassDescriptor> entry : context.getClasses().entrySet()) {
JetClass aClass = entry.getKey();
MutableClassDescriptor classDescriptor = entry.getValue();
for (JetClass aClass : context.getClasses().keySet()) {
if (!context.completeAnalysisNeeded(aClass)) continue;
checkClassOrObject(aClass, classDescriptor);
checkClassOrObject(aClass);
}
for (Map.Entry<JetObjectDeclaration, MutableClassDescriptor> entry : context.getObjects().entrySet()) {
JetObjectDeclaration objectDeclaration = entry.getKey();
MutableClassDescriptor objectDescriptor = entry.getValue();
for (JetObjectDeclaration objectDeclaration : context.getObjects().keySet()) {
if (!context.completeAnalysisNeeded(objectDeclaration)) continue;
checkClassOrObject(objectDeclaration, objectDescriptor);
checkClassOrObject(objectDeclaration);
}
for (Map.Entry<JetNamedFunction, FunctionDescriptorImpl> entry : context.getFunctions().entrySet()) {
JetNamedFunction function = entry.getKey();
FunctionDescriptorImpl functionDescriptor = entry.getValue();
if (!context.completeAnalysisNeeded(function)) continue;
final JetType expectedReturnType = !function.hasBlockBody() && !function.hasDeclaredReturnType()
? NO_EXPECTED_TYPE
: functionDescriptor.getReturnType();
checkFunction(function, functionDescriptor, expectedReturnType);
checkFunction(function, expectedReturnType);
}
for (Map.Entry<JetDeclaration, ConstructorDescriptor> entry : this.context.getConstructors().entrySet()) {
JetDeclaration declaration = entry.getKey();
assert declaration instanceof JetConstructor;
JetConstructor constructor = (JetConstructor) declaration;
ConstructorDescriptor descriptor = entry.getValue();
for (JetConstructor constructor : this.context.getConstructors().keySet()) {
if (!context.completeAnalysisNeeded(constructor)) continue;
checkFunction(constructor, descriptor, JetStandardClasses.getUnitType());
}
for (JetProperty property : context.getProperties().keySet()) {
if (!context.completeAnalysisNeeded(property)) continue;
checkProperty(property);
checkFunction(constructor, JetStandardClasses.getUnitType());
}
}
private void checkClassOrObject(JetClassOrObject klass, MutableClassDescriptor classDescriptor) {
private void checkClassOrObject(JetClassOrObject klass) {
JetFlowInformationProvider flowInformationProvider = new JetFlowInformationProvider((JetDeclaration) klass, (JetExpression) klass, flowDataTraceFactory, context.getTrace());
flowInformationProvider.markUninitializedVariables((JetElement) klass, true, analyzeLocalDeclaration);
flowInformationProvider.markUninitializedVariables((JetElement) klass, true, inLocalDeclaration);
List<JetDeclaration> declarations = klass.getDeclarations();
for (JetDeclaration declaration : declarations) {
if (declaration instanceof JetProperty) {
JetProperty property = (JetProperty) declaration;
DeclarationDescriptor descriptor = context.getTrace().get(BindingContext.DECLARATION_TO_DESCRIPTOR, property);
assert descriptor instanceof PropertyDescriptor;
PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor;
for (JetPropertyAccessor accessor : property.getAccessors()) {
PropertyAccessorDescriptor accessorDescriptor = accessor.isGetter()
? propertyDescriptor.getGetter()
: propertyDescriptor.getSetter();
assert accessorDescriptor != null;
flowInformationProvider.checkDefiniteReturn(accessor, accessorDescriptor.getReturnType());
}
}
}
}
private void checkFunction(JetDeclarationWithBody function, FunctionDescriptor functionDescriptor, final @NotNull JetType expectedReturnType) {
private void checkFunction(JetDeclarationWithBody function, final @NotNull JetType expectedReturnType) {
assert function instanceof JetDeclaration;
JetExpression bodyExpression = function.getBodyExpression();
if (bodyExpression == null) return;
JetFlowInformationProvider flowInformationProvider = new JetFlowInformationProvider((JetDeclaration) function, bodyExpression, flowDataTraceFactory, context.getTrace());
final boolean blockBody = function.hasBlockBody();
List<JetElement> unreachableElements = Lists.newArrayList();
flowInformationProvider.collectUnreachableExpressions(function.asElement(), unreachableElements);
flowInformationProvider.checkDefiniteReturn(function, expectedReturnType);
// This is needed in order to highlight only '1 < 2' and not '1', '<' and '2' as well
final Set<JetElement> rootUnreachableElements = JetPsiUtil.findRootExpressions(unreachableElements);
for (JetElement element : rootUnreachableElements) {
context.getTrace().report(UNREACHABLE_CODE.on(element));
}
List<JetExpression> returnedExpressions = Lists.newArrayList();
flowInformationProvider.collectReturnExpressions(function.asElement(), returnedExpressions);
boolean nothingReturned = returnedExpressions.isEmpty();
returnedExpressions.remove(function); // This will be the only "expression" if the body is empty
final JetScope scope = this.context.getDeclaringScopes().get(function);
if (expectedReturnType != NO_EXPECTED_TYPE && !JetStandardClasses.isUnit(expectedReturnType) && returnedExpressions.isEmpty() && !nothingReturned) {
context.getTrace().report(RETURN_TYPE_MISMATCH.on(bodyExpression, expectedReturnType));
}
for (JetExpression returnedExpression : returnedExpressions) {
returnedExpression.accept(new JetVisitorVoid() {
@Override
public void visitReturnExpression(JetReturnExpression expression) {
if (!blockBody) {
context.getTrace().report(RETURN_IN_FUNCTION_WITH_EXPRESSION_BODY.on(expression));
}
}
@Override
public void visitExpression(JetExpression expression) {
if (blockBody && expectedReturnType != NO_EXPECTED_TYPE && !JetStandardClasses.isUnit(expectedReturnType) && !rootUnreachableElements.contains(expression)) {
JetType type = typeInferrerServices.getType(scope, expression, expectedReturnType);
if (type == null || !JetStandardClasses.isNothing(type)) {
context.getTrace().report(NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY.on(expression));
}
}
}
});
}
flowInformationProvider.markUninitializedVariables(function.asElement(), false, analyzeLocalDeclaration);
if (((JetDeclaration) function).hasModifier(JetTokens.INLINE_KEYWORD)) {
//inline functions after M1
// flowInformationProvider.markNotOnlyInvokedFunctionVariables(function.asElement(), functionDescriptor.getValueParameters());
}
}
private void checkProperty(JetProperty property) {
JetExpression initializer = property.getInitializer();
if (initializer == null) return;
new JetFlowInformationProvider(property, initializer, flowDataTraceFactory, context.getTrace());
flowInformationProvider.markUninitializedVariables(function.asElement(), false, inLocalDeclaration);
}
}
@@ -33,7 +33,7 @@ import java.util.Set;
private final Map<JetDeclaration, JetScope> declaringScopes = Maps.newHashMap();
private final Map<JetNamedFunction, FunctionDescriptorImpl> functions = Maps.newLinkedHashMap();
private final Map<JetDeclaration, ConstructorDescriptor> constructors = Maps.newLinkedHashMap();
private final Map<JetConstructor, ConstructorDescriptor> constructors = Maps.newLinkedHashMap();
private final Map<JetProperty, PropertyDescriptor> properties = Maps.newLinkedHashMap();
private final Set<PropertyDescriptor> primaryConstructorParameterProperties = Sets.newHashSet();
@@ -107,7 +107,7 @@ import java.util.Set;
return primaryConstructorParameterProperties;
}
public Map<JetDeclaration, ConstructorDescriptor> getConstructors() {
public Map<JetConstructor, ConstructorDescriptor> getConstructors() {
return constructors;
}
@@ -143,7 +143,7 @@ public class ClosureExpressionsTypingVisitor extends ExpressionTypingVisitor {
if (functionTypeExpected) {
returnType = JetStandardClasses.getReturnType(expectedType);
}
returnType = context.getServices().getBlockReturnedType(functionInnerScope, functionLiteral.getBodyExpression(), CoercionStrategy.COERCION_TO_UNIT, context.replaceExpectedType(returnType));
returnType = context.getServices().getBlockReturnedType(functionInnerScope, functionLiteral.getBodyExpression(), CoercionStrategy.COERCION_TO_UNIT, context.replaceExpectedType(returnType).replaceExpectedReturnType(returnType));
}
JetType safeReturnType = returnType == null ? ErrorUtils.createErrorType("<return type>") : returnType;
functionDescriptor.setReturnType(safeReturnType);
@@ -55,16 +55,3 @@ error:
sink:
<SINK> NEXT:[] PREV:[<END>]
=====================
== k ==
val k = 34
---------------------
l0:
<START> NEXT:[r(34)] PREV:[]
r(34) NEXT:[<END>] PREV:[<START>]
l1:
<END> NEXT:[<SINK>] PREV:[r(34)]
error:
<ERROR> NEXT:[] PREV:[]
sink:
<SINK> NEXT:[] PREV:[<END>]
=====================
@@ -387,16 +387,3 @@ error:
sink:
<SINK> NEXT:[] PREV:[<END>]
=====================
== a ==
val a: Int = 1
---------------------
l0:
<START> NEXT:[r(1)] PREV:[]
r(1) NEXT:[<END>] PREV:[<START>]
l1:
<END> NEXT:[<SINK>] PREV:[r(1)]
error:
<ERROR> NEXT:[] PREV:[]
sink:
<SINK> NEXT:[] PREV:[<END>]
=====================
@@ -5,7 +5,7 @@ fun unitEmpty() : Unit {}
fun unitEmptyReturn() : Unit {return}
fun unitIntReturn() : Unit {return <!TYPE_MISMATCH!>1<!>}
fun unitUnitReturn() : Unit {return ()}
fun test1() : Any = {<!RETURN_NOT_ALLOWED, RETURN_IN_FUNCTION_WITH_EXPRESSION_BODY!>return<!>}
fun test1() : Any = {return}
fun test2() : Any = @a {return@a 1}
fun test3() : Any { <!RETURN_TYPE_MISMATCH!>return<!> }
@@ -0,0 +1,30 @@
//KT-456 No check for obligatory return in getters
namespace kt456
class A() {
val i: Int
get() : Int <!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>{ //no error
}<!>
}
//more tests
class B() {
val i: Int
get() <!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>{ //no error
}<!>
}
class C() {
val i : Int
get() : Int {
try {
doSmth()
}
finally {
<!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>doSmth()<!>
}
}
}
fun doSmth() {}
@@ -5,7 +5,7 @@ fun unitEmpty() : Unit {}
fun unitEmptyReturn() : Unit {return}
fun unitIntReturn() : Unit {return <error>1</error>}
fun unitUnitReturn() : Unit {return ()}
fun test1() : Any = {<error>return</error>}
fun test1() : Any = {return}
fun test2() : Any = @a {return@a 1}
fun test3() : Any { <error>return</error> }