From 319a250349d20746bfb19fea849dd452a031b18c Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Fri, 15 Apr 2011 21:23:01 +0400 Subject: [PATCH] Sophisticated conventions for 'for'-loop ranges --- .idea/dictionaries/abreslav.xml | 1 + idea/src/jet/lang/Library.jet | 4 +- .../jet/lang/cfg/JetControlFlowProcessor.java | 8 +- .../jet/lang/psi/JetForExpression.java | 3 + .../lang/resolve/ClassDescriptorResolver.java | 2 +- .../resolve/MutableDeclarationDescriptor.java | 2 +- .../jet/lang/resolve/TopDownAnalyzer.java | 2 +- .../jet/lang/types/JetTypeChecker.java | 1 + .../jet/lang/types/JetTypeInferrer.java | 124 ++++++++++++++---- idea/testData/checker/ForRangeConventions.jet | 76 +++++++++++ 10 files changed, 189 insertions(+), 34 deletions(-) create mode 100644 idea/testData/checker/ForRangeConventions.jet diff --git a/.idea/dictionaries/abreslav.xml b/.idea/dictionaries/abreslav.xml index 265db740950..54c8d114ec3 100644 --- a/.idea/dictionaries/abreslav.xml +++ b/.idea/dictionaries/abreslav.xml @@ -4,6 +4,7 @@ accessor dominator inferrer + iterable nondeterministic nullable pseudocode diff --git a/idea/src/jet/lang/Library.jet b/idea/src/jet/lang/Library.jet index 6a8b3d0224b..49e5ab1bd64 100644 --- a/idea/src/jet/lang/Library.jet +++ b/idea/src/jet/lang/Library.jet @@ -9,9 +9,11 @@ class Iterable { fun iterator() : Iterator } -class Array : Iterable { +class Array { fun get(index : Int) : T fun set(index : Int, value : T) : Unit + + fun iterator() : Iterator } virtual class Comparable { diff --git a/idea/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java b/idea/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java index fac01ff688b..3de69c9bfa1 100644 --- a/idea/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java +++ b/idea/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java @@ -188,7 +188,8 @@ public class JetControlFlowProcessor { JetArrayAccessExpression arrayAccessExpression = (JetArrayAccessExpression) left; visitAssignToArrayAccess(expression, arrayAccessExpression); } else if (left instanceof JetQualifiedExpression) { - // + assert !(left instanceof JetPredicateExpression) : left; // TODO + assert !(left instanceof JetHashQualifiedExpression) : left; // TODO JetQualifiedExpression qualifiedExpression = (JetQualifiedExpression) left; value(qualifiedExpression.getReceiverExpression(), false, false); value(expression.getOperationReference(), false, false); @@ -375,7 +376,10 @@ public class JetControlFlowProcessor { @Override public void visitForExpression(JetForExpression expression) { - value(expression.getLoopRange(), false, false); + JetExpression loopRange = expression.getLoopRange(); + if (loopRange != null) { + value(loopRange, false, false); + } // TODO : primitive cases Label loopExitPoint = builder.createUnboundLabel(); builder.nondeterministicJump(loopExitPoint); diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetForExpression.java b/idea/src/org/jetbrains/jet/lang/psi/JetForExpression.java index 423ad6c619a..e6a6585fe2c 100644 --- a/idea/src/org/jetbrains/jet/lang/psi/JetForExpression.java +++ b/idea/src/org/jetbrains/jet/lang/psi/JetForExpression.java @@ -2,6 +2,7 @@ package org.jetbrains.jet.lang.psi; import com.intellij.lang.ASTNode; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.JetNodeTypes; /** @@ -17,10 +18,12 @@ public class JetForExpression extends JetLoopExpression { visitor.visitForExpression(this); } + @Nullable @IfNotParsed public JetParameter getLoopParameter() { return (JetParameter) findChildByType(JetNodeTypes.LOOP_PARAMETER); } + @Nullable @IfNotParsed public JetExpression getLoopRange() { return findExpressionUnder(JetNodeTypes.LOOP_RANGE); } diff --git a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java index 849b8aeafd8..081a142c2c8 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java @@ -70,7 +70,7 @@ public class ClassDescriptorResolver { } public void resolveMutableClassDescriptor(@NotNull JetScope scope, @NotNull JetClass classElement, @NotNull MutableClassDescriptor descriptor) { - descriptor.setName(classElement.getName()); + descriptor.setName(JetPsiUtil.safeName(classElement.getName())); descriptor.getClassHeaderScope().addLabeledDeclaration(descriptor); WritableScope parameterScope = descriptor.getClassHeaderScope(); diff --git a/idea/src/org/jetbrains/jet/lang/resolve/MutableDeclarationDescriptor.java b/idea/src/org/jetbrains/jet/lang/resolve/MutableDeclarationDescriptor.java index 76b5ac43e50..6c0440ebf10 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/MutableDeclarationDescriptor.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/MutableDeclarationDescriptor.java @@ -29,7 +29,7 @@ public abstract class MutableDeclarationDescriptor implements DeclarationDescrip return name; } - public void setName(String name) { + public void setName(@NotNull String name) { this.name = name; } diff --git a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java index 0b6e70069fa..e58a2cbd663 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java @@ -124,7 +124,7 @@ public class TopDownAnalyzer { private WritableScope processClass(@NotNull WritableScope declaringScope, JetClass klass) { MutableClassDescriptor mutableClassDescriptor = new MutableClassDescriptor(semanticServices, declaringScope.getContainingDeclaration(), declaringScope); - mutableClassDescriptor.setName(klass.getName()); + mutableClassDescriptor.setName(JetPsiUtil.safeName(klass.getName())); declaringScope.addClassifierDescriptor(mutableClassDescriptor); diff --git a/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java b/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java index 387eb004aad..9c2d75f1d61 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetTypeChecker.java @@ -75,6 +75,7 @@ public class JetTypeChecker { boolean nullable = false; for (Iterator iterator = typeSet.iterator(); iterator.hasNext();) { JetType type = iterator.next(); + // TODO : This admits 'Nothing?'. Review if (JetStandardClasses.isNothing(type)) { iterator.remove(); } diff --git a/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java b/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java index 5be9feb485d..4e69be720ba 100644 --- a/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java +++ b/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java @@ -1,6 +1,7 @@ package org.jetbrains.jet.lang.types; import com.google.common.collect.ImmutableMap; +import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; @@ -834,43 +835,37 @@ public class JetTypeInferrer { public void visitForExpression(JetForExpression expression) { JetParameter loopParameter = expression.getLoopParameter(); JetExpression loopRange = expression.getLoopRange(); - JetType loopRangeType = getType(scope, loopRange, false); + JetType loopRangeType = null; + if (loopRange != null) { + loopRangeType = getType(scope, loopRange, false); + } JetType expectedParameterType = null; if (loopRangeType != null) { - if (!semanticServices.getTypeChecker().isSubtypeOf(loopRangeType, semanticServices.getStandardLibrary().getIterableType(JetStandardClasses.getNullableAnyType()))) { - semanticServices.getErrorHandler().genericError(loopRange.getNode(), "Expecting an Iterable, but found " + loopRangeType); - } - else { - TypeProjection typeProjection = loopRangeType.getArguments().get(0); - if (!typeProjection.getProjectionKind().allowsOutPosition()) { - expectedParameterType = JetStandardClasses.getDefaultBound(); - } - else { - expectedParameterType = typeProjection.getType(); - } - } + expectedParameterType = checkIterableConvention(loopRangeType, loopRange.getNode()); } WritableScope loopScope = semanticServices.createWritableScope(scope, scope.getContainingDeclaration()); - JetTypeReference typeReference = loopParameter.getTypeReference(); - PropertyDescriptor propertyDescriptor; - if (typeReference != null) { - propertyDescriptor = classDescriptorResolver.resolveValueParameterDescriptor(scope.getContainingDeclaration(), scope, loopParameter); - JetType actualParameterType = propertyDescriptor.getOutType(); - if (expectedParameterType != null && - actualParameterType != null && - !semanticServices.getTypeChecker().isSubtypeOf(expectedParameterType, actualParameterType)) { - semanticServices.getErrorHandler().genericError(typeReference.getNode(), "The loop iterates over values of type " + expectedParameterType + " but the parameter is declared to be " + actualParameterType); + if (loopParameter != null) { + JetTypeReference typeReference = loopParameter.getTypeReference(); + PropertyDescriptor propertyDescriptor; + if (typeReference != null) { + propertyDescriptor = classDescriptorResolver.resolveValueParameterDescriptor(scope.getContainingDeclaration(), scope, loopParameter); + JetType actualParameterType = propertyDescriptor.getOutType(); + if (expectedParameterType != null && + actualParameterType != null && + !semanticServices.getTypeChecker().isSubtypeOf(expectedParameterType, actualParameterType)) { + semanticServices.getErrorHandler().genericError(typeReference.getNode(), "The loop iterates over values of type " + expectedParameterType + " but the parameter is declared to be " + actualParameterType); + } } - } - else { - if (expectedParameterType == null) { - expectedParameterType = ErrorUtils.createErrorType("Error"); + else { + if (expectedParameterType == null) { + expectedParameterType = ErrorUtils.createErrorType("Error"); + } + propertyDescriptor = classDescriptorResolver.resolveValueParameterDescriptor(scope.getContainingDeclaration(), loopParameter, expectedParameterType); } - propertyDescriptor = classDescriptorResolver.resolveValueParameterDescriptor(scope.getContainingDeclaration(), loopParameter, expectedParameterType); + loopScope.addPropertyDescriptor(propertyDescriptor); } - loopScope.addPropertyDescriptor(propertyDescriptor); JetExpression body = expression.getBody(); if (body != null) { @@ -880,6 +875,79 @@ public class JetTypeInferrer { result = JetStandardClasses.getUnitType(); } + @Nullable + private JetType checkIterableConvention(@NotNull JetType type, @NotNull ASTNode reportErrorsOn) { + OverloadResolutionResult iteratorResolutionResult = resolveNoParametersFunction(type, scope, "iterator"); + if (iteratorResolutionResult.isSuccess()) { + JetType iteratorType = iteratorResolutionResult.getFunctionDescriptor().getUnsubstitutedReturnType(); + boolean hasNextFunctionSupported = checkHasNextFunctionSupport(reportErrorsOn, iteratorType); + boolean hasNextPropertySupported = checkHasNextPropertySupport(reportErrorsOn, iteratorType); + if (hasNextFunctionSupported && hasNextPropertySupported) { + // TODO : overload resolution rules impose priorities here??? + semanticServices.getErrorHandler().genericError(reportErrorsOn, "An ambiguity between 'iterator().hasNext()' function and 'iterator().hasNext()' property"); + } + else if (!hasNextFunctionSupported && !hasNextPropertySupported) { + semanticServices.getErrorHandler().genericError(reportErrorsOn, "Loop range must have an 'iterator().hasNext()' function or an 'iterator().hasNext' property"); + } + + OverloadResolutionResult nextResolutionResult = resolveNoParametersFunction(iteratorType, scope, "next"); + if (nextResolutionResult.isAmbiguity()) { + semanticServices.getErrorHandler().genericError(reportErrorsOn, "Method 'iterator().next()' is ambiguous for this expression"); + } else if (nextResolutionResult.isNothing()) { + semanticServices.getErrorHandler().genericError(reportErrorsOn, "Loop range must have an 'iterator().next()' method"); + } else { + return nextResolutionResult.getFunctionDescriptor().getUnsubstitutedReturnType(); + } + } + else { + String errorMessage = "For-loop range must have an iterator() method"; + if (iteratorResolutionResult.isAmbiguity()) { + errorMessage = "Method 'iterator()' is ambiguous for this expression"; + } + semanticServices.getErrorHandler().genericError(reportErrorsOn, errorMessage); + } + return null; + } + + private boolean checkHasNextFunctionSupport(@NotNull ASTNode reportErrorsOn, @NotNull JetType iteratorType) { + OverloadResolutionResult hasNextResolutionResult = resolveNoParametersFunction(iteratorType, scope, "hasNext"); + if (hasNextResolutionResult.isAmbiguity()) { + semanticServices.getErrorHandler().genericError(reportErrorsOn, "Method 'iterator().hasNext()' is ambiguous for this expression"); + } else if (hasNextResolutionResult.isNothing()) { + return false; + } else { + JetType hasNextReturnType = hasNextResolutionResult.getFunctionDescriptor().getUnsubstitutedReturnType(); + if (!isBoolean(hasNextReturnType)) { + semanticServices.getErrorHandler().genericError(reportErrorsOn, "The 'iterator().hasNext()' method of the loop range must return Boolean, but returns " + hasNextReturnType); + } + } + return true; + } + + private boolean checkHasNextPropertySupport(@NotNull ASTNode reportErrorsOn, @NotNull JetType iteratorType) { + PropertyDescriptor hasNextProperty = iteratorType.getMemberScope().getProperty("hasNext"); + // TODO :extension properties + if (hasNextProperty == null) { + return false; + } else { + JetType hasNextReturnType = hasNextProperty.getOutType(); + if (hasNextReturnType == null) { + // TODO : accessibility + semanticServices.getErrorHandler().genericError(reportErrorsOn, "The 'iterator().hasNext' property of the loop range must be readable"); + } + else if (!isBoolean(hasNextReturnType)) { + semanticServices.getErrorHandler().genericError(reportErrorsOn, "The 'iterator().hasNext' property of the loop range must return Boolean, but returns " + hasNextReturnType); + } + } + return true; + } + + @NotNull + private OverloadResolutionResult resolveNoParametersFunction(@NotNull JetType receiverType, @NotNull JetScope scope, @NotNull String name) { + OverloadDomain overloadDomain = semanticServices.getOverloadResolver().getOverloadDomain(receiverType, scope, name); + return overloadDomain.getFunctionDescriptorForPositionedArguments(Collections.emptyList(), Collections.emptyList()); + } + @Override public void visitNewExpression(JetNewExpression expression) { // TODO : type argument inference diff --git a/idea/testData/checker/ForRangeConventions.jet b/idea/testData/checker/ForRangeConventions.jet new file mode 100644 index 00000000000..86e66214c61 --- /dev/null +++ b/idea/testData/checker/ForRangeConventions.jet @@ -0,0 +1,76 @@ +class NotRange1 { + +} + +class NotRange2 { + fun iterator() : Unit +} + +class ImproperIterator1 { + fun hasNext() : Boolean +} + +class NotRange3 { + fun iterator() : ImproperIterator1 +} + +class ImproperIterator2 { + fun next() : Boolean +} + +class NotRange4 { + fun iterator() : ImproperIterator2 +} + +class ImproperIterator3 { + fun hasNext() : Int + fun next() : Int +} + +class NotRange5 { + fun iterator() : ImproperIterator3 +} + +class AmbiguousHasNextIterator { + fun hasNext() : Boolean + val hasNext : Boolean + fun next() : Int +} + +class NotRange6 { + fun iterator() : AmbiguousHasNextIterator +} + +class ImproperIterator4 { + val hasNext : Int + fun next() : Int +} + +class NotRange7 { + fun iterator() : ImproperIterator3 +} + +class GoodIterator { + fun hasNext() : Boolean + fun next() : Int +} + +class Range0 { + fun iterator() : GoodIterator +} + +class Range1 { + fun iterator() : Iterator +} + +fun test() { + for (i in new NotRange1()); + for (i in new NotRange2()); + for (i in new NotRange3()); + for (i in new NotRange4()); + for (i in new NotRange5()); + for (i in new NotRange6()); + for (i in new NotRange7()); + for (i in new Range0()); + for (i in new Range1()); +}