From 94f00509e25fe233e7c6c77f6ff92118b6a11339 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Fri, 17 Jun 2011 18:03:30 +0400 Subject: [PATCH] JET-34 Require a return statement in a function with a block body JET-77 Require a return type annotation for non-Unit returning functions with block bodies --- .../lang/cfg/JetFlowInformationProvider.java | 18 ++ .../lang/resolve/ClassDescriptorResolver.java | 45 ++- .../jet/lang/resolve/TopDownAnalyzer.java | 25 +- .../jet/lang/types/JetStandardClasses.java | 5 + .../jet/lang/types/JetTypeInferrer.java | 280 ++++++++++++------ idea/testData/checker/ExtensionFunctions.jet | 2 +- idea/testData/checker/FunctionReturnTypes.jet | 24 +- idea/testData/checker/QualifiedThis.jet | 2 +- idea/testData/checker/UnreachableCode.jet | 22 +- idea/testData/checker/When.jet | 2 +- .../infos/PropertiesWithBackingFields.jet | 4 +- idea/testData/codegen/classes/inheritance.jet | 2 +- .../codegen/controlStructures/for.jet | 2 +- .../codegen/controlStructures/forInArray.jet | 2 +- idea/testData/codegen/localProperty.jet | 2 +- .../jet/codegen/PatternMatchingTest.java | 2 +- .../jet/types/JetTypeCheckerTest.java | 8 +- 17 files changed, 303 insertions(+), 144 deletions(-) diff --git a/idea/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java b/idea/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java index 8dff812a8ef..8e27cbd262b 100644 --- a/idea/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java +++ b/idea/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java @@ -17,6 +17,11 @@ public interface JetFlowInformationProvider { throw new UnsupportedOperationException(); } + @Override + public void collectReturnExpressions(@NotNull JetElement subroutine, @NotNull Collection returnedExpressions) { + throw new UnsupportedOperationException(); + } + @Override public void collectUnreachableExpressions(@NotNull JetElement subroutine, @NotNull Collection unreachableElements) { throw new UnsupportedOperationException(); @@ -40,6 +45,11 @@ public interface JetFlowInformationProvider { } + @Override + public void collectReturnExpressions(@NotNull JetElement subroutine, @NotNull Collection returnedExpressions) { + + } + @Override public void collectUnreachableExpressions(@NotNull JetElement subroutine, @NotNull Collection unreachableElements) { @@ -57,11 +67,19 @@ public interface JetFlowInformationProvider { }; + /** + * Collects expressions returned from the given subroutine and 'return;' expressions + */ void collectReturnedInformation( @NotNull JetElement subroutine, @NotNull Collection returnedExpressions, @NotNull Collection elementsReturningUnit); + /** + * Collects all 'return ...' expressions that return from the given subroutine and all the expressions that precede the exit point + */ + void collectReturnExpressions(@NotNull JetElement subroutine, @NotNull Collection returnedExpressions); + void collectUnreachableExpressions( @NotNull JetElement subroutine, @NotNull Collection unreachableElements); diff --git a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java index 0319a18403c..15c8cfcc013 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java @@ -197,7 +197,7 @@ public class ClassDescriptorResolver { @Override protected JetType compute() { JetFlowInformationProvider flowInformationProvider = computeFlowData(function, bodyExpression); - return semanticServices.getTypeInferrer(trace, flowInformationProvider).getFunctionReturnType(scope, function, functionDescriptor); + return semanticServices.getTypeInferrer(trace, flowInformationProvider).inferFunctionReturnType(scope, function, functionDescriptor); } }); } @@ -525,7 +525,7 @@ public class ClassDescriptorResolver { LazyValue lazyValue = new LazyValue() { @Override protected JetType compute() { - return semanticServices.getTypeInferrer(trace, JetFlowInformationProvider.THROW_EXCEPTION).safeGetType(scope, initializer, false); + return semanticServices.getTypeInferrer(trace, JetFlowInformationProvider.THROW_EXCEPTION).safeGetType(scope, initializer, false, null); // TODO } }; if (allowDeferred) { @@ -733,7 +733,7 @@ public class ClassDescriptorResolver { } } - public JetFlowInformationProvider computeFlowData(@NotNull JetElement declaration, @NotNull JetExpression bodyExpression) { + public JetFlowInformationProvider computeFlowData(@NotNull JetElement declaration, @NotNull final JetExpression bodyExpression) { final JetPseudocodeTrace pseudocodeTrace = flowDataTraceFactory.createTrace(declaration); final Map pseudocodeMap = new HashMap(); final Map representativeInstructions = new HashMap(); @@ -779,6 +779,45 @@ public class ClassDescriptorResolver { processPreviousInstructions(exitInstruction, new HashSet(), returnedExpressions, elementsReturningUnit); } + @Override + public void collectReturnExpressions(@NotNull JetElement subroutine, @NotNull final Collection returnedExpressions) { + Pseudocode pseudocode = pseudocodeMap.get(subroutine); + assert pseudocode != null; + + SubroutineExitInstruction exitInstruction = pseudocode.getExitInstruction(); + for (Instruction previousInstruction : exitInstruction.getPreviousInstructions()) { + previousInstruction.accept(new InstructionVisitor() { + @Override + public void visitReturnValue(ReturnValueInstruction instruction) { + returnedExpressions.add((JetExpression) instruction.getElement()); + } + + @Override + public void visitReturnNoValue(ReturnNoValueInstruction instruction) { + returnedExpressions.add((JetExpression) instruction.getElement()); + } + + + + @Override + public void visitJump(AbstractJumpInstruction instruction) { + // Nothing + } + + @Override + public void visitInstruction(Instruction instruction) { + if (instruction instanceof JetElementInstruction) { + JetElementInstruction elementInstruction = (JetElementInstruction) instruction; + returnedExpressions.add((JetExpression) elementInstruction.getElement()); + } + else { + throw new IllegalStateException(instruction + " precedes the exit point"); + } + } + }); + } + } + @Override public void collectUnreachableExpressions(@NotNull JetElement subroutine, @NotNull Collection unreachableElements) { Pseudocode pseudocode = pseudocodeMap.get(subroutine); diff --git a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java index 1d02b9eacc5..92506851fb9 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java @@ -1,8 +1,6 @@ package org.jetbrains.jet.lang.resolve; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; @@ -33,7 +31,6 @@ public class TopDownAnalyzer { private final Map constructors = Maps.newLinkedHashMap(); private final Map properties = new LinkedHashMap(); private final Map declaringScopes = Maps.newHashMap(); - private final Multimap declaringScopesToProperties = ArrayListMultimap.create(); private final Set primaryConstructorParameterProperties = Sets.newHashSet(); private final JetSemanticServices semanticServices; @@ -572,7 +569,7 @@ public class TopDownAnalyzer { JetExpression delegateExpression = specifier.getDelegateExpression(); if (delegateExpression != null) { JetScope scope = scopeForConstructor == null ? descriptor.getScopeForMemberResolution() : scopeForConstructor; - JetType type = typeInferrer.getType(scope, delegateExpression, false); + JetType type = typeInferrer.getType(scope, delegateExpression, false, null); JetType supertype = trace.getBindingContext().resolveTypeReference(specifier.getTypeReference()); if (type != null && !semanticServices.getTypeChecker().isSubtypeOf(type, supertype)) { // TODO : Convertible? trace.getErrorHandler().typeMismatch(delegateExpression, supertype, type); @@ -644,7 +641,7 @@ public class TopDownAnalyzer { final JetScope scopeForConstructor = getInnerScopeForConstructor(primaryConstructor, classDescriptor.getScopeForMemberResolution(), true); JetTypeInferrer typeInferrer = semanticServices.getTypeInferrer(traceForConstructors, JetFlowInformationProvider.NONE); // TODO : flow for (JetClassInitializer anonymousInitializer : anonymousInitializers) { - typeInferrer.getType(scopeForConstructor, anonymousInitializer.getBody(), true); + typeInferrer.getType(scopeForConstructor, anonymousInitializer.getBody(), true, null); } } else { @@ -730,7 +727,7 @@ public class TopDownAnalyzer { JetFlowInformationProvider flowInformationProvider = classDescriptorResolver.computeFlowData(declaration, bodyExpression); JetTypeInferrer typeInferrer = semanticServices.getTypeInferrer(traceForConstructors, flowInformationProvider); - typeInferrer.getType(functionInnerScope, bodyExpression, true); + typeInferrer.getType(functionInnerScope, bodyExpression, true, null); } } @@ -860,7 +857,7 @@ public class TopDownAnalyzer { private void resolvePropertyInitializer(JetProperty property, PropertyDescriptor propertyDescriptor, JetExpression initializer, JetScope scope) { JetFlowInformationProvider flowInformationProvider = classDescriptorResolver.computeFlowData(property, initializer); // TODO : flow JET-15 JetTypeInferrer typeInferrer = semanticServices.getTypeInferrer(traceForConstructors, flowInformationProvider); - JetType type = typeInferrer.getType(getPropertyDeclarationInnerScope(scope, propertyDescriptor), initializer, false); + JetType type = typeInferrer.getType(getPropertyDeclarationInnerScope(scope, propertyDescriptor), initializer, false, null); JetType expectedType; PropertySetterDescriptor setter = propertyDescriptor.getSetter(); @@ -905,20 +902,6 @@ public class TopDownAnalyzer { JetTypeInferrer typeInferrer = semanticServices.getTypeInferrer(trace, flowInformationProvider); typeInferrer.checkFunctionReturnType(declaringScope, function, functionDescriptor); - - List unreachableElements = new ArrayList(); - flowInformationProvider.collectUnreachableExpressions(function.asElement(), unreachableElements); - - // This is needed in order to highlight only '1 < 2' and not '1', '<' and '2' as well - Set rootElements = JetPsiUtil.findRootExpressions(unreachableElements); - - // TODO : (return 1) || (return 2) -- only || and right of it is unreachable - // TODO : try {return 1} finally {return 2}. Currently 'return 1' is reported as unreachable, - // though it'd better be reported more specifically - - for (JetElement element : rootElements) { - trace.getErrorHandler().genericError(element.getNode(), "Unreachable code"); - } } assert functionDescriptor.getUnsubstitutedReturnType() != null; diff --git a/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java b/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java index c32f66c2ad9..89d981b1f3a 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetStandardClasses.java @@ -274,6 +274,11 @@ public class JetStandardClasses { type.getConstructor() == NOTHING_CLASS.getTypeConstructor(); } + public static boolean isUnit(@NotNull JetType type) { + return !(type instanceof NamespaceType) && + type.getConstructor() == UNIT_TYPE.getConstructor(); + } + public static JetType getTupleType(List annotations, List arguments) { if (annotations.isEmpty() && arguments.isEmpty()) { return getUnitType(); diff --git a/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java b/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java index 1281337438e..345cc2e4c36 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java @@ -26,6 +26,36 @@ import java.util.*; */ public class JetTypeInferrer { + private static final JetType FORBIDDEN = new JetType() { + @NotNull + @Override + public TypeConstructor getConstructor() { + throw new UnsupportedOperationException(); // TODO + } + + @NotNull + @Override + public List getArguments() { + throw new UnsupportedOperationException(); // TODO + } + + @Override + public boolean isNullable() { + throw new UnsupportedOperationException(); // TODO + } + + @NotNull + @Override + public JetScope getMemberScope() { + throw new UnsupportedOperationException(); // TODO + } + + @Override + public List getAnnotations() { + throw new UnsupportedOperationException(); // TODO + } + }; + private static final Map unaryOperationNames = ImmutableMap.builder() .put(JetTokens.PLUSPLUS, "inc") .put(JetTokens.MINUSMINUS, "dec") @@ -33,8 +63,8 @@ public class JetTypeInferrer { .put(JetTokens.MINUS, "minus") .put(JetTokens.EXCL, "not") .build(); - private static final Map binaryOperationNames = new HashMap(); + static { binaryOperationNames.put(JetTokens.MUL, "times"); binaryOperationNames.put(JetTokens.PLUS, "plus"); @@ -44,12 +74,12 @@ public class JetTypeInferrer { binaryOperationNames.put(JetTokens.ARROW, "arrow"); binaryOperationNames.put(JetTokens.RANGE, "rangeTo"); } - private static final Set comparisonOperations = new HashSet(Arrays.asList(JetTokens.LT, JetTokens.GT, JetTokens.LTEQ, JetTokens.GTEQ)); private static final Set equalsOperations = new HashSet(Arrays.asList(JetTokens.EQEQ, JetTokens.EXCLEQ)); - private static final Set inOperations = new HashSet(Arrays.asList(JetTokens.IN_KEYWORD, JetTokens.NOT_IN)); + private static final Set inOperations = new HashSet(Arrays.asList(JetTokens.IN_KEYWORD, JetTokens.NOT_IN)); public static final Map assignmentOperationNames = new HashMap(); + static { assignmentOperationNames.put(JetTokens.MULTEQ, "timesAssign"); assignmentOperationNames.put(JetTokens.DIVEQ, "divAssign"); @@ -57,8 +87,8 @@ public class JetTypeInferrer { assignmentOperationNames.put(JetTokens.PLUSEQ, "plusAssign"); assignmentOperationNames.put(JetTokens.MINUSEQ, "minusAssign"); } - private static final Map assignmentOperationCounterparts = new HashMap(); + static { assignmentOperationCounterparts.put(JetTokens.MULTEQ, JetTokens.MUL); assignmentOperationCounterparts.put(JetTokens.DIVEQ, JetTokens.DIV); @@ -66,7 +96,6 @@ public class JetTypeInferrer { assignmentOperationCounterparts.put(JetTokens.PLUSEQ, JetTokens.PLUS); assignmentOperationCounterparts.put(JetTokens.MINUSEQ, JetTokens.MINUS); } - private final BindingTrace trace; private final JetSemanticServices semanticServices; private final TypeResolver typeResolver; @@ -84,8 +113,8 @@ public class JetTypeInferrer { } @NotNull - public JetType safeGetType(@NotNull final JetScope scope, @NotNull JetExpression expression, final boolean preferBlock) { - JetType type = getType(scope, expression, preferBlock); + public JetType safeGetType(@NotNull final JetScope scope, @NotNull JetExpression expression, boolean preferBlock, @Nullable JetType expectedType) { + JetType type = getType(scope, expression, preferBlock, expectedType); if (type != null) { return type; } @@ -93,12 +122,12 @@ public class JetTypeInferrer { } @Nullable - public JetType getType(@NotNull final JetScope scope, @NotNull JetExpression expression, final boolean preferBlock) { - return new TypeInferrerVisitor(scope, preferBlock, DataFlowInfo.getEmpty()).getType(expression); + public JetType getType(@NotNull final JetScope scope, @NotNull JetExpression expression, boolean preferBlock, @Nullable JetType expectedType) { + return new TypeInferrerVisitor(scope, preferBlock, DataFlowInfo.getEmpty(), expectedType, null).getType(expression); } - public JetType getTypeWithNamespaces(@NotNull final JetScope scope, @NotNull JetExpression expression, final boolean preferBlock) { - return new TypeInferrerVisitorWithNamespaces(scope, preferBlock, DataFlowInfo.getEmpty()).getType(expression); + public JetType getTypeWithNamespaces(@NotNull final JetScope scope, @NotNull JetExpression expression, boolean preferBlock) { + return new TypeInferrerVisitorWithNamespaces(scope, preferBlock, DataFlowInfo.getEmpty(), null, null).getType(expression); } @Nullable @@ -277,64 +306,112 @@ public class JetTypeInferrer { }; } - @NotNull - public JetType getFunctionReturnType(@NotNull JetScope outerScope, JetDeclarationWithBody function, FunctionDescriptor functionDescriptor) { - Map typeMap = collectReturnedExpressions(outerScope, function, functionDescriptor); - Collection types = typeMap.values(); - return types.isEmpty() ? JetStandardClasses.getNothingType() : semanticServices.getTypeChecker().commonSupertype(types); - } - -// private JetType getCachedType(@NotNull JetExpression expression) { -//// assert typeCache.containsKey(expression) : "No type cached for " + expression.getText(); -// return typeCache.get(expression); -// } - public void checkFunctionReturnType(@NotNull JetScope outerScope, @NotNull JetDeclarationWithBody function, @NotNull FunctionDescriptor functionDescriptor) { - Map typeMap = collectReturnedExpressions(outerScope, function, functionDescriptor); - if (typeMap.isEmpty()) { - return; // The function returns Nothing - } - JetType expectedReturnType = functionDescriptor.getUnsubstitutedReturnType(); - for (Map.Entry entry : typeMap.entrySet()) { - JetType actualType = entry.getValue(); - JetElement element = entry.getKey(); - JetTypeChecker typeChecker = semanticServices.getTypeChecker(); - if (!typeChecker.isSubtypeOf(actualType, expectedReturnType)) { - if (typeChecker.isConvertibleBySpecialConversion(actualType, expectedReturnType)) { - if (expectedReturnType.getConstructor().equals(JetStandardClasses.getUnitType().getConstructor()) - && element.getParent() instanceof JetReturnExpression) { - trace.getErrorHandler().genericError(element.getNode(), "This function must return a value of type Unit"); - } - } - else { - if (element == function) { - JetExpression bodyExpression = function.getBodyExpression(); - assert bodyExpression != null; - trace.getErrorHandler().genericError(bodyExpression.getNode(), "This function must return a value of type " + expectedReturnType); - } - else if (element instanceof JetExpression) { - JetExpression expression = (JetExpression) element; - trace.getErrorHandler().typeMismatch(expression, expectedReturnType, actualType); - } - else { - trace.getErrorHandler().genericError(element.getNode(), "This function must return a value of type " + expectedReturnType); - } - } - } - } - } - - private Map collectReturnedExpressions(JetScope outerScope, JetDeclarationWithBody function, FunctionDescriptor functionDescriptor) { + final JetType expectedReturnType = functionDescriptor.getUnsubstitutedReturnType(); JetExpression bodyExpression = function.getBodyExpression(); assert bodyExpression != null; JetScope functionInnerScope = FunctionDescriptorUtil.getFunctionInnerScope(outerScope, functionDescriptor, trace); - getType(functionInnerScope, bodyExpression, function.hasBlockBody()); + new TypeInferrerVisitor(functionInnerScope, function.hasBlockBody(), DataFlowInfo.getEmpty(), null, expectedReturnType).getType(bodyExpression); + + List unreachableElements = Lists.newArrayList(); + flowInformationProvider.collectUnreachableExpressions(function.asElement(), unreachableElements); + + // This is needed in order to highlight only '1 < 2' and not '1', '<' and '2' as well + final Set rootUnreachableElements = JetPsiUtil.findRootExpressions(unreachableElements); + + // TODO : (return 1) || (return 2) -- only || and right of it is unreachable + // TODO : try {return 1} finally {return 2}. Currently 'return 1' is reported as unreachable, + // though it'd better be reported more specifically + + for (JetElement element : rootUnreachableElements) { + trace.getErrorHandler().genericError(element.getNode(), "Unreachable code"); + } + + final boolean blockBody = function.hasBlockBody(); + List 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 + + if (!JetStandardClasses.isUnit(expectedReturnType) && returnedExpressions.isEmpty() && !nothingReturned) { + trace.getErrorHandler().genericError(bodyExpression.getNode(), "This function must return a value of type " + expectedReturnType); + } + + for (JetExpression returnedExpression : returnedExpressions) { + returnedExpression.accept(new JetVisitor() { + @Override + public void visitReturnExpression(JetReturnExpression expression) { + if (!blockBody) { + trace.getErrorHandler().genericError(expression.getNode(), "Returns are not allowed for functions with expression body. Use block body in '{...}'"); + } + } + + @Override + public void visitExpression(JetExpression expression) { + if (blockBody && !JetStandardClasses.isUnit(expectedReturnType) && !rootUnreachableElements.contains(expression)) { + trace.getErrorHandler().genericError(expression.getNode(), "A 'return' expression required in a function with a block body ('{...}')"); + } + } + }); + } +// Map typeMap = collectReturnedExpressionsWithTypes(outerScope, function, functionDescriptor, expectedReturnType); +// if (typeMap.isEmpty()) { +// return; // The function returns Nothing +// } +// for (Map.Entry entry : typeMap.entrySet()) { +// JetType actualType = entry.getValue(); +// JetElement element = entry.getKey(); +// JetTypeChecker typeChecker = semanticServices.getTypeChecker(); +// if (!typeChecker.isSubtypeOf(actualType, expectedReturnType)) { +// if (typeChecker.isConvertibleBySpecialConversion(actualType, expectedReturnType)) { +// if (expectedReturnType.getConstructor().equals(JetStandardClasses.getUnitType().getConstructor()) +// && element.getParent() instanceof JetReturnExpression) { +// trace.getErrorHandler().genericError(element.getNode(), "This function must return a value of type Unit"); +// } +// } +// else { +// if (element == function) { +// JetExpression bodyExpression = function.getBodyExpression(); +// assert bodyExpression != null; +// trace.getErrorHandler().genericError(bodyExpression.getNode(), "This function must return a value of type " + expectedReturnType); +// } +// else if (element instanceof JetExpression) { +// JetExpression expression = (JetExpression) element; +// trace.getErrorHandler().typeMismatch(expression, expectedReturnType, actualType); +// } +// else { +// trace.getErrorHandler().genericError(element.getNode(), "This function must return a value of type " + expectedReturnType); +// } +// } +// } +// } + } + + @NotNull + public JetType inferFunctionReturnType(@NotNull JetScope outerScope, JetDeclarationWithBody function, FunctionDescriptor functionDescriptor) { + Map typeMap = collectReturnedExpressionsWithTypes(outerScope, function, functionDescriptor); + Collection types = typeMap.values(); + return types.isEmpty() + ? JetStandardClasses.getNothingType() + : semanticServices.getTypeChecker().commonSupertype(types); + } + + private Map collectReturnedExpressionsWithTypes( + JetScope outerScope, + JetDeclarationWithBody function, + FunctionDescriptor functionDescriptor) { + JetExpression bodyExpression = function.getBodyExpression(); + assert bodyExpression != null; + JetScope functionInnerScope = FunctionDescriptorUtil.getFunctionInnerScope(outerScope, functionDescriptor, trace); + new TypeInferrerVisitor(functionInnerScope, function.hasBlockBody(), DataFlowInfo.getEmpty(), null, FORBIDDEN).getType(bodyExpression); Collection returnedExpressions = new ArrayList(); Collection elementsReturningUnit = new ArrayList(); flowInformationProvider.collectReturnedInformation(function.asElement(), returnedExpressions, elementsReturningUnit); Map typeMap = new HashMap(); for (JetExpression returnedExpression : returnedExpressions) { - JetType cachedType = trace.getBindingContext().getExpressionType(returnedExpression);// getCachedType(returnedExpression); + JetType cachedType = trace.getBindingContext().getExpressionType(returnedExpression); trace.removeStatementRecord(returnedExpression); if (cachedType != null) { typeMap.put(returnedExpression, cachedType); @@ -347,22 +424,22 @@ public class JetTypeInferrer { } @Nullable - private JetType getBlockReturnedType(@NotNull JetScope outerScope, @NotNull List block, DataFlowInfo dataFlowInfo) { + private JetType getBlockReturnedType(@NotNull JetScope outerScope, @NotNull List block, DataFlowInfo dataFlowInfo, JetType extectedReturnType) { if (block.isEmpty()) { return JetStandardClasses.getUnitType(); } DeclarationDescriptor containingDescriptor = outerScope.getContainingDeclaration(); WritableScope scope = new WritableScopeImpl(outerScope, containingDescriptor, trace.getErrorHandler()); - return getBlockReturnedTypeWithWritableScope(scope, block, dataFlowInfo); + return getBlockReturnedTypeWithWritableScope(scope, block, dataFlowInfo, extectedReturnType); } - private JetType getBlockReturnedTypeWithWritableScope(@NotNull WritableScope scope, @NotNull List block, DataFlowInfo dataFlowInfo) { + private JetType getBlockReturnedTypeWithWritableScope(@NotNull WritableScope scope, @NotNull List block, DataFlowInfo dataFlowInfo, JetType extectedReturnType) { if (block.isEmpty()) { return JetStandardClasses.getUnitType(); } - TypeInferrerVisitorWithWritableScope blockLevelVisitor = new TypeInferrerVisitorWithWritableScope(scope, true, dataFlowInfo); + TypeInferrerVisitorWithWritableScope blockLevelVisitor = new TypeInferrerVisitorWithWritableScope(scope, true, dataFlowInfo, null, extectedReturnType); JetType result = null; for (JetElement statement : block) { @@ -378,7 +455,7 @@ public class JetTypeInferrer { // newScope = scope; // } if (newDataFlowInfo != dataFlowInfo) {// || newScope != scope) { - blockLevelVisitor = new TypeInferrerVisitorWithWritableScope(scope, true, newDataFlowInfo); + blockLevelVisitor = new TypeInferrerVisitorWithWritableScope(scope, true, newDataFlowInfo, null, extectedReturnType); } else { blockLevelVisitor.resetResult(); // TODO : maybe it's better to recreate the visitors with the same scope? @@ -449,7 +526,7 @@ public class JetTypeInferrer { List valueArgumentTypes = new ArrayList(); for (JetExpression valueArgument : positionedValueArguments) { - valueArgumentTypes.add(safeGetType(scope, valueArgument, false)); + valueArgumentTypes.add(safeGetType(scope, valueArgument, false, null)); // TODO } OverloadResolutionResult resolutionResult = overloadDomain.getFunctionDescriptorForPositionedArguments(typeArguments, valueArgumentTypes); @@ -602,12 +679,18 @@ public class JetTypeInferrer { protected JetType result; protected DataFlowInfo resultDataFlowInfo; + + protected final JetType extectedType; + protected final JetType extectedReturnType; + // protected WritableScope resultScope; - private TypeInferrerVisitor(@NotNull JetScope scope, boolean preferBlock, @NotNull DataFlowInfo dataFlowInfo) { + private TypeInferrerVisitor(@NotNull JetScope scope, boolean preferBlock, @NotNull DataFlowInfo dataFlowInfo, @Nullable JetType expectedType, @Nullable JetType expectedReturnType) { this.scope = scope; this.preferBlock = preferBlock; this.dataFlowInfo = dataFlowInfo; + this.extectedType = expectedType; + this.extectedReturnType = expectedReturnType; } @Nullable @@ -637,7 +720,7 @@ public class JetTypeInferrer { visitor = this; } else { - visitor = createNew(scope, preferBlock, dataFlowInfo); + visitor = createNew(scope, preferBlock, dataFlowInfo, extectedType, extectedReturnType); } JetType type = visitor.getType(expression); visitor.result = null; @@ -645,8 +728,8 @@ public class JetTypeInferrer { } @NotNull - public TypeInferrerVisitor createNew(JetScope scope, boolean preferBlock, DataFlowInfo dataFlowInfo) { - return new TypeInferrerVisitor(scope, preferBlock, dataFlowInfo); + public TypeInferrerVisitor createNew(@NotNull JetScope scope, boolean preferBlock, @NotNull DataFlowInfo dataFlowInfo, @Nullable JetType expectedType, @Nullable JetType expectedReturnType) { + return new TypeInferrerVisitor(scope, preferBlock, dataFlowInfo, expectedType, expectedReturnType); } @Nullable @@ -803,7 +886,7 @@ public class JetTypeInferrer { public void visitFunctionLiteralExpression(JetFunctionLiteralExpression expression) { if (preferBlock && !expression.hasParameterSpecification()) { trace.recordBlock(expression); - result = getBlockReturnedType(scope, expression.getBody(), dataFlowInfo); + result = getBlockReturnedType(scope, expression.getBody(), dataFlowInfo, extectedReturnType); return; } @@ -852,7 +935,7 @@ public class JetTypeInferrer { writableScope.addVariableDescriptor(variableDescriptor); } writableScope.setThisType(receiverType); - returnType = getBlockReturnedType(writableScope, expression.getBody(), dataFlowInfo); + returnType = getBlockReturnedType(writableScope, expression.getBody(), dataFlowInfo, extectedReturnType); } JetType safeReturnType = returnType == null ? ErrorUtils.createErrorType("") : returnType; functionDescriptor.setReturnType(safeReturnType); @@ -929,10 +1012,26 @@ public class JetTypeInferrer { @Override public void visitReturnExpression(JetReturnExpression expression) { + if (extectedReturnType == FORBIDDEN) { + trace.getErrorHandler().genericError(expression.getNode(), "'return' is not allowed here"); + return; + } JetExpression returnedExpression = expression.getReturnedExpression(); + JetType returnedType = JetStandardClasses.getUnitType(); if (returnedExpression != null) { - getType(scope, returnedExpression, false); + returnedType = getType(scope, returnedExpression, false); + } + else { + if (extectedReturnType != null && !JetStandardClasses.isUnit(extectedReturnType)) { + trace.getErrorHandler().genericError(expression.getNode(), "This function must return a value of type " + extectedReturnType); + } + } + + if (extectedReturnType != null && returnedType != null) { + if (!semanticServices.getTypeChecker().isSubtypeOf(returnedType, extectedReturnType)) { + trace.getErrorHandler().typeMismatch(returnedExpression == null ? expression : returnedExpression, extectedReturnType, returnedType); + } } result = JetStandardClasses.getNothingType(); @@ -950,7 +1049,7 @@ public class JetTypeInferrer { @Override public void visitTypeofExpression(JetTypeofExpression expression) { - JetType type = safeGetType(scope, expression.getBaseExpression(), false); + JetType type = safeGetType(scope, expression.getBaseExpression(), false, null); // TODO result = semanticServices.getStandardLibrary().getTypeInfoType(type); } @@ -1006,7 +1105,7 @@ public class JetTypeInferrer { List entries = expression.getEntries(); List types = new ArrayList(); for (JetExpression entry : entries) { - types.add(safeGetType(scope, entry, false)); + types.add(safeGetType(scope, entry, false, null)); // TODO } // TODO : labels result = JetStandardClasses.getTupleType(types); @@ -1114,7 +1213,7 @@ public class JetTypeInferrer { @Override public void visitBlockExpression(JetBlockExpression expression) { - result = getBlockReturnedType(scope, expression.getStatements(), dataFlowInfo); + result = getBlockReturnedType(scope, expression.getStatements(), dataFlowInfo, extectedReturnType); } @Override @@ -1122,7 +1221,7 @@ public class JetTypeInferrer { // TODO :change scope according to the bound value in the when header final JetExpression subjectExpression = expression.getSubjectExpression(); - final JetType subjectType = subjectExpression != null ? safeGetType(scope, subjectExpression, false) : ErrorUtils.createErrorType("Unknown type"); + final JetType subjectType = subjectExpression != null ? safeGetType(scope, subjectExpression, false, null) : ErrorUtils.createErrorType("Unknown type"); final VariableDescriptor variableDescriptor = subjectExpression != null ? getVariableDescriptorFromSimpleName(subjectExpression) : null; // TODO : exhaustive patterns @@ -1564,7 +1663,7 @@ public class JetTypeInferrer { if (!function.hasParameterSpecification()) { WritableScope writableScope = newWritableScopeImpl(); conditionScope = writableScope; - getBlockReturnedTypeWithWritableScope(writableScope, function.getBody(), dataFlowInfo); + getBlockReturnedTypeWithWritableScope(writableScope, function.getBody(), dataFlowInfo, extectedReturnType); trace.recordBlock(function); } else { getType(scope, body, true); @@ -1573,7 +1672,7 @@ public class JetTypeInferrer { else if (body != null) { WritableScope writableScope = newWritableScopeImpl(); conditionScope = writableScope; - getBlockReturnedTypeWithWritableScope(writableScope, Collections.singletonList(body), dataFlowInfo); + getBlockReturnedTypeWithWritableScope(writableScope, Collections.singletonList(body), dataFlowInfo, extectedReturnType); } JetExpression condition = expression.getCondition(); checkCondition(conditionScope, condition); @@ -1588,7 +1687,7 @@ public class JetTypeInferrer { return newWritableScopeImpl(scope); } - private WritableScopeImpl newWritableScopeImpl(JetScope scopeToExtend) { + protected WritableScopeImpl newWritableScopeImpl(JetScope scopeToExtend) { return new WritableScopeImpl(scopeToExtend, scopeToExtend.getContainingDeclaration(), trace.getErrorHandler()); } @@ -1722,7 +1821,7 @@ public class JetTypeInferrer { // TODO : functions as values JetExpression selectorExpression = expression.getSelectorExpression(); JetExpression receiverExpression = expression.getReceiverExpression(); - JetType receiverType = new TypeInferrerVisitorWithNamespaces(scope, false, dataFlowInfo).getType(receiverExpression); + JetType receiverType = new TypeInferrerVisitorWithNamespaces(scope, false, dataFlowInfo, null, null).getType(receiverExpression); if (receiverType != null) { ErrorHandlerWithRegions errorHandler = trace.getErrorHandler(); errorHandler.openRegion(); @@ -2099,7 +2198,7 @@ public class JetTypeInferrer { @Nullable protected List getTypes(JetScope scope, List indexExpressions) { List argumentTypes = new ArrayList(); - TypeInferrerVisitor typeInferrerVisitor = new TypeInferrerVisitor(scope, false, dataFlowInfo); + TypeInferrerVisitor typeInferrerVisitor = new TypeInferrerVisitor(scope, false, dataFlowInfo, null, null); for (JetExpression indexExpression : indexExpressions) { JetType type = typeInferrerVisitor.getType(indexExpression); if (type == null) { @@ -2190,8 +2289,8 @@ public class JetTypeInferrer { } private class TypeInferrerVisitorWithNamespaces extends TypeInferrerVisitor { - private TypeInferrerVisitorWithNamespaces(@NotNull JetScope scope, boolean preferBlock, DataFlowInfo dataFlowInfo) { - super(scope, preferBlock, dataFlowInfo); + private TypeInferrerVisitorWithNamespaces(@NotNull JetScope scope, boolean preferBlock, @NotNull DataFlowInfo dataFlowInfo, @Nullable JetType expectedType, @Nullable JetType expectedReturnType) { + super(scope, preferBlock, dataFlowInfo, expectedType, expectedReturnType); } @Override @@ -2201,8 +2300,8 @@ public class JetTypeInferrer { @NotNull @Override - public TypeInferrerVisitor createNew(JetScope scope, boolean preferBlock, DataFlowInfo dataFlowInfo) { - return new TypeInferrerVisitorWithNamespaces(scope, preferBlock, dataFlowInfo); + public TypeInferrerVisitor createNew(@NotNull JetScope scope, boolean preferBlock, @NotNull DataFlowInfo dataFlowInfo, @Nullable JetType expectedType, @Nullable JetType expectedReturnType) { + return new TypeInferrerVisitorWithNamespaces(scope, preferBlock, dataFlowInfo, expectedType, expectedReturnType); } @Override @@ -2221,8 +2320,8 @@ public class JetTypeInferrer { private class TypeInferrerVisitorWithWritableScope extends TypeInferrerVisitor { private final WritableScope scope; - public TypeInferrerVisitorWithWritableScope(@NotNull WritableScope scope, boolean preferBlock, DataFlowInfo dataFlowInfo) { - super(scope, preferBlock, dataFlowInfo); + public TypeInferrerVisitorWithWritableScope(@NotNull WritableScope scope, boolean preferBlock, DataFlowInfo dataFlowInfo, @Nullable JetType expectedType, @Nullable JetType expectedReturnType) { + super(scope, preferBlock, dataFlowInfo, expectedType, expectedReturnType); this.scope = scope; } @@ -2356,8 +2455,9 @@ public class JetTypeInferrer { @NotNull @Override - public TypeInferrerVisitor createNew(JetScope scope, boolean preferBlock, DataFlowInfo dataFlowInfo) { - return new TypeInferrerVisitorWithWritableScope(new WritableScopeImpl(scope, scope.getContainingDeclaration(), trace.getErrorHandler()), preferBlock, dataFlowInfo); + public TypeInferrerVisitor createNew(@NotNull JetScope scope, boolean preferBlock, @NotNull DataFlowInfo dataFlowInfo, @Nullable JetType expectedType, @Nullable JetType expectedReturnType) { + WritableScopeImpl writableScope = newWritableScopeImpl(scope); + return new TypeInferrerVisitorWithWritableScope(writableScope, preferBlock, dataFlowInfo, expectedType, expectedReturnType); } } } diff --git a/idea/testData/checker/ExtensionFunctions.jet b/idea/testData/checker/ExtensionFunctions.jet index c06234d0548..669b51c0cbe 100644 --- a/idea/testData/checker/ExtensionFunctions.jet +++ b/idea/testData/checker/ExtensionFunctions.jet @@ -8,7 +8,7 @@ fun T.foo(x : E, y : A) : T { this?.minus(this) - this + return this } class A diff --git a/idea/testData/checker/FunctionReturnTypes.jet b/idea/testData/checker/FunctionReturnTypes.jet index 7f20a2b69e4..1857f065665 100644 --- a/idea/testData/checker/FunctionReturnTypes.jet +++ b/idea/testData/checker/FunctionReturnTypes.jet @@ -5,6 +5,19 @@ fun unitEmpty() : Unit {} fun unitEmptyReturn() : Unit {return} fun unitIntReturn() : Unit {return 1} fun unitUnitReturn() : Unit {return ()} +fun test1() : Any = {return} +fun test2() : Any = @a {return@a 1} +fun test3() : Any { return } + +fun foo(expr: StringBuilder): Int { + val c = 'a' + when(c) { + '\0' => throw Exception("zero") + else => throw Exception("nonzero" + c) + } +} + + fun unitShort() : Unit = () fun unitShortConv() : Unit = 1 fun unitShortNull() : Unit = null @@ -13,7 +26,8 @@ fun intEmpty() : Int {} fun intShortInfer() = 1 fun intShort() : Int = 1 //fun intBlockInfer() {1} -fun intBlock() : Int {1} +fun intBlock() : Int {return 1} +fun intBlock() : Int {1} fun blockReturnUnitMismatch() : Int {return} fun blockReturnValueTypeMismatch() : Int {return 3.4} @@ -40,7 +54,7 @@ fun blockAndAndMismatch() : Int { (return true) || (return false) } fun blockReturnValueTypeMatch() : Int { - return if (1 > 2) 1.0 else 2.0 + return if (1 > 2) 1.0 else 2.0 } fun blockReturnValueTypeMatch() : Int { return if (1 > 2) 1 @@ -79,7 +93,7 @@ fun blockReturnValueTypeMatch() : Int { 1.0 } fun blockReturnValueTypeMatch() : Int { - if (1 > 2) + return if (1 > 2) 1 } fun blockReturnValueTypeMatch() : Int { @@ -88,6 +102,6 @@ fun blockReturnValueTypeMatch() : Int { } fun blockReturnValueTypeMatch() : Int { if (1 > 2) - 1 - else 1.0 + return 1 + else return 1.0 } diff --git a/idea/testData/checker/QualifiedThis.jet b/idea/testData/checker/QualifiedThis.jet index 296d5d8c33d..21113c22f0d 100644 --- a/idea/testData/checker/QualifiedThis.jet +++ b/idea/testData/checker/QualifiedThis.jet @@ -29,7 +29,7 @@ namespace closures { val y = this@A : A val z = this : B val Int.xx = this : Int - fun Char.xx() { + fun Char.xx() : Any { this : Char val a = {Double.() => this : Double + this@xx : Char} val b = @a{Double.() => this@a : Double + this@xx : Char} diff --git a/idea/testData/checker/UnreachableCode.jet b/idea/testData/checker/UnreachableCode.jet index 2b6d9731365..b34b281adcc 100644 --- a/idea/testData/checker/UnreachableCode.jet +++ b/idea/testData/checker/UnreachableCode.jet @@ -58,7 +58,7 @@ fun t4break(a : Boolean) : Int { break } while (a) - 1 + return 1 } fun t5() : Int { @@ -67,7 +67,7 @@ fun t5() : Int { 2 } while (1 > 2) - 1 + return 1 } fun t6() : Int { @@ -75,7 +75,7 @@ fun t6() : Int { return 1 2 } - 1 + return 1 } fun t6break() : Int { @@ -83,7 +83,7 @@ fun t6break() : Int { break 2 } - 1 + return 1 } fun t7(b : Int) : Int { @@ -91,7 +91,7 @@ fun t7(b : Int) : Int { return 1 2 } - 1 + return 1 } fun t7break(b : Int) : Int { @@ -99,7 +99,7 @@ fun t7break(b : Int) : Int { return 1 2 } - 1 + return 1 } fun t7() : Int { @@ -110,7 +110,7 @@ fun t7() : Int { catch (e : Any) { 2 } - 1 // this is OK, like in Java + return 1 // this is OK, like in Java } fun t8() : Int { @@ -122,24 +122,24 @@ fun t8() : Int { return 1 2 } - 1 + return 1 } fun blockAndAndMismatch() : Boolean { (return true) || (return false) - true + return true } fun tf() : Int { try {return 1} finally{return 1} - 1 + return 1 } fun failtest(a : Int) : Int { if (fail() || true) { } - 1 + return 1 } fun foo(a : Nothing) : Unit { diff --git a/idea/testData/checker/When.jet b/idea/testData/checker/When.jet index 34d83e363a5..53177aea4ec 100644 --- a/idea/testData/checker/When.jet +++ b/idea/testData/checker/When.jet @@ -16,7 +16,7 @@ fun foo() : Int { ?.equals(1) => 1 else => 1 } - when (x?:null) { + return when (x?:null) { .equals(1) => 1 ?.equals(1).equals(2) => 1 } diff --git a/idea/testData/checker/infos/PropertiesWithBackingFields.jet b/idea/testData/checker/infos/PropertiesWithBackingFields.jet index 702fee7d19b..942763a1ee9 100644 --- a/idea/testData/checker/infos/PropertiesWithBackingFields.jet +++ b/idea/testData/checker/infos/PropertiesWithBackingFields.jet @@ -14,9 +14,9 @@ class Test() { val c3 : Int get() { return 1 } val c4 : Int - get() { 1 } + get() = 1 val c5 : Int - get() { $c5 + 1 } + get() = $c5 + 1 abstract var y : Int abstract var y1 : Int get diff --git a/idea/testData/codegen/classes/inheritance.jet b/idea/testData/codegen/classes/inheritance.jet index 8db0245b94d..54445e1f860 100644 --- a/idea/testData/codegen/classes/inheritance.jet +++ b/idea/testData/codegen/classes/inheritance.jet @@ -29,5 +29,5 @@ fun box() : String { val p4 = P4(240, y) if (p4.x + p4.y != 239) return "FAIL #7" - "OK" + return "OK" } \ No newline at end of file diff --git a/idea/testData/codegen/controlStructures/for.jet b/idea/testData/codegen/controlStructures/for.jet index ffaf8a6489b..588baba360e 100644 --- a/idea/testData/codegen/controlStructures/for.jet +++ b/idea/testData/codegen/controlStructures/for.jet @@ -5,5 +5,5 @@ fun concat(l: List): String? { for(s in l) { sb.append(s) } - sb.toString() + return sb.toString() } diff --git a/idea/testData/codegen/controlStructures/forInArray.jet b/idea/testData/codegen/controlStructures/forInArray.jet index 132ab47a283..70c508458ab 100644 --- a/idea/testData/codegen/controlStructures/forInArray.jet +++ b/idea/testData/codegen/controlStructures/forInArray.jet @@ -3,5 +3,5 @@ fun concat(l: Array): String? { for(s in l) { sb.append(s) } - sb.toString() + return sb.toString() } diff --git a/idea/testData/codegen/localProperty.jet b/idea/testData/codegen/localProperty.jet index a40915d37b3..2c14afc8fb4 100644 --- a/idea/testData/codegen/localProperty.jet +++ b/idea/testData/codegen/localProperty.jet @@ -2,5 +2,5 @@ fun f(a:Int) : Int { val x = 42 val y = 50 - y + return y } diff --git a/idea/tests/org/jetbrains/jet/codegen/PatternMatchingTest.java b/idea/tests/org/jetbrains/jet/codegen/PatternMatchingTest.java index bfd4208b3c6..87a2980c25e 100644 --- a/idea/tests/org/jetbrains/jet/codegen/PatternMatchingTest.java +++ b/idea/tests/org/jetbrains/jet/codegen/PatternMatchingTest.java @@ -57,7 +57,7 @@ public class PatternMatchingTest extends CodegenTestCase { } public void testNoReturnType() throws Exception { - loadText("fun foo(x: String) = when(x) { is * => return \"x\" }"); + loadText("fun foo(x: String) = when(x) { is * => \"x\" }"); Method foo = generateFunction(); assertEquals("x", foo.invoke(null, "")); } diff --git a/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java b/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java index ec7f9200eb8..020c32cb633 100644 --- a/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java +++ b/idea/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java @@ -493,14 +493,14 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { private void assertType(String expression, JetType expectedType) { Project project = getProject(); JetExpression jetExpression = JetChangeUtil.createExpression(project, expression); - JetType type = semanticServices.getTypeInferrer(JetTestUtils.DUMMY, JetFlowInformationProvider.NONE).getType(classDefinitions.BASIC_SCOPE, jetExpression, false); + JetType type = semanticServices.getTypeInferrer(JetTestUtils.DUMMY, JetFlowInformationProvider.NONE).getType(classDefinitions.BASIC_SCOPE, jetExpression, false, null); assertTrue(type + " != " + expectedType, type.equals(expectedType)); } private void assertErrorType(String expression) { Project project = getProject(); JetExpression jetExpression = JetChangeUtil.createExpression(project, expression); - JetType type = semanticServices.getTypeInferrer(JetTestUtils.DUMMY, JetFlowInformationProvider.NONE).safeGetType(classDefinitions.BASIC_SCOPE, jetExpression, false); + JetType type = semanticServices.getTypeInferrer(JetTestUtils.DUMMY, JetFlowInformationProvider.NONE).safeGetType(classDefinitions.BASIC_SCOPE, jetExpression, false, null); assertTrue("Error type expected but " + type + " returned", ErrorUtils.isErrorType(type)); } @@ -523,7 +523,7 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { private void assertType(JetScope scope, String expression, String expectedTypeStr) { Project project = getProject(); JetExpression jetExpression = JetChangeUtil.createExpression(project, expression); - JetType type = semanticServices.getTypeInferrer(JetTestUtils.DUMMY, JetFlowInformationProvider.NONE).getType(scope, jetExpression, false); + JetType type = semanticServices.getTypeInferrer(JetTestUtils.DUMMY, JetFlowInformationProvider.NONE).getType(scope, jetExpression, false, null); JetType expectedType = expectedTypeStr == null ? null : makeType(expectedTypeStr); assertEquals(expectedType, type); } @@ -613,4 +613,4 @@ public class JetTypeCheckerTest extends LightDaemonAnalyzerTestCase { } }; } -} \ No newline at end of file +}