From 891aebf3bc82978e96d8125719a6cfd91ccf79d5 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Tue, 17 May 2011 22:27:35 +0400 Subject: [PATCH] Basic support for when() expressions --- grammar/src/when.grm | 2 +- idea/src/org/jetbrains/jet/JetNodeTypes.java | 18 +-- .../jet/lang/cfg/JetControlFlowProcessor.java | 58 ++++++++- .../lang/parsing/JetExpressionParsing.java | 22 +++- .../jetbrains/jet/lang/psi/JetVisitor.java | 16 +++ .../jet/lang/psi/JetWhenCondition.java | 13 ++ .../jet/lang/psi/JetWhenConditionCall.java | 35 ++++++ .../jet/lang/psi/JetWhenConditionInRange.java | 45 +++++++ .../lang/psi/JetWhenConditionIsPattern.java | 29 +++++ .../psi/JetWhenConditionWithExpression.java | 25 ++++ .../jetbrains/jet/lang/psi/JetWhenEntry.java | 5 + .../jet/lang/psi/JetWhenExpression.java | 6 + .../jet/lang/types/JetTypeInferrer.java | 115 +++++++++++++++--- idea/testData/checker/When.jet | 18 +++ idea/testData/psi/NewlinesInParentheses.txt | 6 +- idea/testData/psi/When.txt | 6 +- idea/testData/psi/When_ERR.txt | 15 ++- 17 files changed, 389 insertions(+), 45 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/lang/psi/JetWhenCondition.java create mode 100644 idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionCall.java create mode 100644 idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionInRange.java create mode 100644 idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionIsPattern.java create mode 100644 idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionWithExpression.java create mode 100644 idea/testData/checker/When.jet diff --git a/grammar/src/when.grm b/grammar/src/when.grm index 259410e9390..9256fbe612b 100644 --- a/grammar/src/when.grm +++ b/grammar/src/when.grm @@ -16,7 +16,7 @@ whenConditionIf whenCondition : expression - : "." postfixUnaryExpression typeArguments? valueArguments? + : ("." | "?.") postfixUnaryExpression typeArguments? valueArguments? : ("in" | "!in") expression : ("is" | "!is") isRHS ; diff --git a/idea/src/org/jetbrains/jet/JetNodeTypes.java b/idea/src/org/jetbrains/jet/JetNodeTypes.java index 5c67a142f9d..638892a60d3 100644 --- a/idea/src/org/jetbrains/jet/JetNodeTypes.java +++ b/idea/src/org/jetbrains/jet/JetNodeTypes.java @@ -111,22 +111,22 @@ public interface JetNodeTypes { JetNodeType HASH_QUALIFIED_EXPRESSION = new JetNodeType("HASH_QUALIFIED_EXPRESSION", JetHashQualifiedExpression.class); JetNodeType SAFE_ACCESS_EXPRESSION = new JetNodeType("SAFE_ACCESS_EXPRESSION", JetSafeQualifiedExpression.class); JetNodeType PREDICATE_EXPRESSION = new JetNodeType("PREDICATE_EXPRESSION", JetPredicateExpression.class); - JetNodeType DECOMPOSER_PATTERN = new JetNodeType("DECOMPOSER_PATTERN"); - JetNodeType TUPLE_PATTERN = new JetNodeType("TUPLE_PATTERN"); + JetNodeType DECOMPOSER_PATTERN = new JetNodeType("DECOMPOSER_PATTERN", JetPattern.class); // TODO + JetNodeType TUPLE_PATTERN = new JetNodeType("TUPLE_PATTERN", JetPattern.class); // TODO JetNodeType OBJECT_LITERAL = new JetNodeType("OBJECT_LITERAL", JetObjectLiteralExpression.class); JetNodeType ROOT_NAMESPACE = new JetNodeType("ROOT_NAMESPACE", JetRootNamespaceExpression.class); JetNodeType DECOMPOSER_ARGUMENT_LIST = new JetNodeType("DECOMPOSER_ARGUMENT_LIST"); JetNodeType DECOMPOSER_ARGUMENT = new JetNodeType("DECOMPOSER_ARGUMENT"); JetNodeType TYPE_PATTERN = new JetNodeType("TYPE_PATTERN", JetTypePattern.class); - JetNodeType EXPRESSION_PATTERN = new JetNodeType("EXPRESSION_PATTERN"); - JetNodeType BINDING_PATTERN = new JetNodeType("BINDING_PATTERN"); - JetNodeType WILDCARD_PATTERN = new JetNodeType("WILDCARD_PATTERN"); + JetNodeType EXPRESSION_PATTERN = new JetNodeType("EXPRESSION_PATTERN", JetPattern.class); // TODO + JetNodeType BINDING_PATTERN = new JetNodeType("BINDING_PATTERN", JetPattern.class); // TODO + JetNodeType WILDCARD_PATTERN = new JetNodeType("WILDCARD_PATTERN", JetPattern.class); // TODO JetNodeType WHEN = new JetNodeType("WHEN", JetWhenExpression.class); JetNodeType WHEN_ENTRY = new JetNodeType("WHEN_ENTRY", JetWhenEntry.class); - JetNodeType WHEN_CONDITION_IN_RANGE = new JetNodeType("WHEN_CONDITION_IN_RANGE"); - JetNodeType WHEN_CONDITION_IS_PATTERN = new JetNodeType("WHEN_CONDITION_IS_PATTERN"); - JetNodeType WHEN_CONDITION_CALL = new JetNodeType("WHEN_CONDITION_CALL"); - JetNodeType WHEN_CONDITION_EXPRESSION = new JetNodeType("WHEN_CONDITION_EXPRESSION"); + JetNodeType WHEN_CONDITION_IN_RANGE = new JetNodeType("WHEN_CONDITION_IN_RANGE", JetWhenConditionInRange.class); + JetNodeType WHEN_CONDITION_IS_PATTERN = new JetNodeType("WHEN_CONDITION_IS_PATTERN", JetWhenConditionIsPattern.class); + JetNodeType WHEN_CONDITION_CALL = new JetNodeType("WHEN_CONDITION_CALL", JetWhenConditionCall.class); + JetNodeType WHEN_CONDITION_EXPRESSION = new JetNodeType("WHEN_CONDITION_EXPRESSION", JetWhenConditionWithExpression.class); JetNodeType TUPLE_PATTERN_ENTRY = new JetNodeType("TUPLE_PATTERN_ENTRY"); JetNodeType NULLABLE_TYPE = new JetNodeType("NULLABLE_TYPE", JetNullableType.class); JetNodeType TYPE_PROJECTION = new JetNodeType("TYPE_PROJECTION", JetTypeProjection.class); diff --git a/idea/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java b/idea/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java index 923bbe808a2..9e39691fa81 100644 --- a/idea/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java +++ b/idea/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java @@ -96,7 +96,8 @@ public class JetControlFlowProcessor { this.inCondition = inCondition; } - private void value(@NotNull JetElement element, boolean preferBlock, boolean inCondition) { + private void value(@Nullable JetElement element, boolean preferBlock, boolean inCondition) { + if (element == null) return; CFPVisitor visitor; if (this.preferBlock == preferBlock && this.inCondition == inCondition) { visitor = this; @@ -609,6 +610,61 @@ public class JetControlFlowProcessor { builder.read(expression); } + @Override + public void visitWhenExpression(JetWhenExpression expression) { + // TODO : no more than one else + // TODO : else must be the last + JetExpression subjectExpression = expression.getSubjectExpression(); + if (subjectExpression != null) { + value(subjectExpression, false, inCondition); + } + + Label nextLabel = builder.createUnboundLabel(); + for (JetWhenEntry whenEntry : expression.getEntries()) { + if (whenEntry.getSubWhen() != null) throw new UnsupportedOperationException(); // TODO + + if (whenEntry.isElseContinue()) throw new UnsupportedOperationException(); // TODO + + JetWhenCondition condition = whenEntry.getCondition(); + if (condition != null) { + condition.accept(new JetVisitor() { + @Override + public void visitWhenConditionWithExpression(JetWhenConditionWithExpression condition) { + value(condition.getExpression(), false, inCondition); // TODO : inCondition? + } + + @Override + public void visitWhenConditionCall(JetWhenConditionCall condition) { + value(condition.getCallSuffixExpression(), false, inCondition); // TODO : inCondition? + } + + @Override + public void visitWhenConditionInRange(JetWhenConditionInRange condition) { + value(condition.getRangeExpression(), false, inCondition); // TODO : inCondition? + value(condition.getOperationReference(), false, inCondition); // TODO : inCondition? + // TODO : read the call to contains()... + } + + @Override + public void visitWhenConditionIsPattern(JetWhenConditionIsPattern condition) { + super.visitWhenConditionIsPattern(condition); // TODO + } + + @Override + public void visitJetElement(JetElement elem) { + throw new UnsupportedOperationException(); + } + }); + } + + builder.nondeterministicJump(nextLabel); + + value(whenEntry.getExpression(), true, inCondition); + builder.bindLabel(nextLabel); + nextLabel = builder.createUnboundLabel(); + } + } + @Override public void visitTypeProjection(JetTypeProjection typeProjection) { // TODO : Support Type Arguments. Class object may be initialized at this point"); diff --git a/idea/src/org/jetbrains/jet/lang/parsing/JetExpressionParsing.java b/idea/src/org/jetbrains/jet/lang/parsing/JetExpressionParsing.java index 0f4efbf19a3..7ddb116c475 100644 --- a/idea/src/org/jetbrains/jet/lang/parsing/JetExpressionParsing.java +++ b/idea/src/org/jetbrains/jet/lang/parsing/JetExpressionParsing.java @@ -706,7 +706,7 @@ public class JetExpressionParsing extends AbstractJetParsing { /* * whenCondition * : expression - * : "." postfixExpression typeArguments? valueArguments? + * : ("." | "?." postfixExpression typeArguments? valueArguments? * : ("in" | "!in") expression * : ("is" | "!is") isRHS * ; @@ -715,7 +715,10 @@ public class JetExpressionParsing extends AbstractJetParsing { PsiBuilder.Marker condition = mark(); myBuilder.disableNewlines(); if (at(IN_KEYWORD) || at(NOT_IN)) { + PsiBuilder.Marker mark = mark(); advance(); // IN_KEYWORD or NOT_IN + mark.done(OPERATION_REFERENCE); + if (atSet(WHEN_CONDITION_RECOVERY_SET_WITH_DOUBLE_ARROW)) { error("Expecting an element"); @@ -732,13 +735,20 @@ public class JetExpressionParsing extends AbstractJetParsing { parsePattern(); } condition.done(WHEN_CONDITION_IS_PATTERN); - } else if (at(DOT)) { - advance(); // DOT + } else if (at(DOT) || at(SAFE_ACCESS)) { + advance(); // DOT or SAFE_ACCESS + PsiBuilder.Marker mark = mark(); parsePostfixExpression(); - myJetParsing.parseTypeArgumentList(); - if (at(LPAR)) { - parseValueArgumentList(); + if (parseCallSuffix()) { + mark.done(CALL_EXPRESSION); } + else { + mark.drop(); + } +// myJetParsing.parseTypeArgumentList(); +// if (at(LPAR)) { +// parseValueArgumentList(); +// } condition.done(WHEN_CONDITION_CALL); } else { if (atSet(WHEN_CONDITION_RECOVERY_SET_WITH_DOUBLE_ARROW)) { diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetVisitor.java b/idea/src/org/jetbrains/jet/lang/psi/JetVisitor.java index b9924b94211..1f9d202dcc9 100644 --- a/idea/src/org/jetbrains/jet/lang/psi/JetVisitor.java +++ b/idea/src/org/jetbrains/jet/lang/psi/JetVisitor.java @@ -349,4 +349,20 @@ public class JetVisitor extends PsiElementVisitor { public void visitIsExpression(JetIsExpression expression) { visitExpression(expression); } + + public void visitWhenConditionWithExpression(JetWhenConditionWithExpression condition) { + visitJetElement(condition); + } + + public void visitWhenConditionCall(JetWhenConditionCall condition) { + visitJetElement(condition); + } + + public void visitWhenConditionIsPattern(JetWhenConditionIsPattern condition) { + visitJetElement(condition); + } + + public void visitWhenConditionInRange(JetWhenConditionInRange condition) { + visitJetElement(condition); + } } diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetWhenCondition.java b/idea/src/org/jetbrains/jet/lang/psi/JetWhenCondition.java new file mode 100644 index 00000000000..2569a947059 --- /dev/null +++ b/idea/src/org/jetbrains/jet/lang/psi/JetWhenCondition.java @@ -0,0 +1,13 @@ +package org.jetbrains.jet.lang.psi; + +import com.intellij.lang.ASTNode; +import org.jetbrains.annotations.NotNull; + +/** + * @author abreslav + */ +public abstract class JetWhenCondition extends JetElement { + public JetWhenCondition(@NotNull ASTNode node) { + super(node); + } +} diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionCall.java b/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionCall.java new file mode 100644 index 00000000000..490398f47bd --- /dev/null +++ b/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionCall.java @@ -0,0 +1,35 @@ +package org.jetbrains.jet.lang.psi; + +import com.intellij.lang.ASTNode; +import com.intellij.psi.tree.TokenSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lexer.JetTokens; + +/** + * @author abreslav + */ +public class JetWhenConditionCall extends JetWhenCondition { + public JetWhenConditionCall(@NotNull ASTNode node) { + super(node); + } + + public boolean isSafeCall() { + return getNode().findChildByType(JetTokens.SAFE_ACCESS) != null; + } + + @NotNull + public ASTNode getOperationTokenNode() { + return getNode().findChildByType(TokenSet.create(JetTokens.SAFE_ACCESS, JetTokens.DOT)); + } + + @Nullable @IfNotParsed + public JetExpression getCallSuffixExpression() { + return findChildByClass(JetExpression.class); + } + + @Override + public void accept(@NotNull JetVisitor visitor) { + visitor.visitWhenConditionCall(this); + } +} diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionInRange.java b/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionInRange.java new file mode 100644 index 00000000000..10bb6239a97 --- /dev/null +++ b/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionInRange.java @@ -0,0 +1,45 @@ +package org.jetbrains.jet.lang.psi; + +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.JetNodeTypes; +import org.jetbrains.jet.lexer.JetTokens; + +/** + * @author abreslav + */ +public class JetWhenConditionInRange extends JetWhenCondition { + public JetWhenConditionInRange(@NotNull ASTNode node) { + super(node); + } + + public boolean isNegated() { + return getNode().findChildByType(JetTokens.NOT_IN) != null; + } + + @Nullable @IfNotParsed + public JetExpression getRangeExpression() { + // Copied from JetBinaryExpression + ASTNode node = getOperationReference().getNode().getTreeNext(); + while (node != null) { + PsiElement psi = node.getPsi(); + if (psi instanceof JetExpression) { + return (JetExpression) psi; + } + node = node.getTreeNext(); + } + + return null; + } + + @Override + public void accept(@NotNull JetVisitor visitor) { + visitor.visitWhenConditionInRange(this); + } + + public JetSimpleNameExpression getOperationReference() { + return (JetSimpleNameExpression) findChildByType(JetNodeTypes.OPERATION_REFERENCE); + } +} diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionIsPattern.java b/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionIsPattern.java new file mode 100644 index 00000000000..55c0aacf85d --- /dev/null +++ b/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionIsPattern.java @@ -0,0 +1,29 @@ +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.lexer.JetTokens; + +/** + * @author abreslav + */ +public class JetWhenConditionIsPattern extends JetWhenCondition { + public JetWhenConditionIsPattern(@NotNull ASTNode node) { + super(node); + } + + public boolean isNegated() { + return getNode().findChildByType(JetTokens.NOT_IS) != null; + } + + @Nullable @IfNotParsed + public JetPattern getPattern() { + return findChildByClass(JetPattern.class); + } + + @Override + public void accept(@NotNull JetVisitor visitor) { + visitor.visitWhenConditionIsPattern(this); + } +} diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionWithExpression.java b/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionWithExpression.java new file mode 100644 index 00000000000..5f79f08cbfa --- /dev/null +++ b/idea/src/org/jetbrains/jet/lang/psi/JetWhenConditionWithExpression.java @@ -0,0 +1,25 @@ +package org.jetbrains.jet.lang.psi; + +import com.intellij.lang.ASTNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author abreslav + */ +public class JetWhenConditionWithExpression extends JetWhenCondition { + public JetWhenConditionWithExpression(@NotNull ASTNode node) { + super(node); + } + + @Nullable + @IfNotParsed + public JetExpression getExpression() { + return findChildByClass(JetExpression.class); + } + + @Override + public void accept(@NotNull JetVisitor visitor) { + visitor.visitWhenConditionWithExpression(this); + } +} diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetWhenEntry.java b/idea/src/org/jetbrains/jet/lang/psi/JetWhenEntry.java index 8fa37ab930a..c1eb346dfcf 100644 --- a/idea/src/org/jetbrains/jet/lang/psi/JetWhenEntry.java +++ b/idea/src/org/jetbrains/jet/lang/psi/JetWhenEntry.java @@ -37,4 +37,9 @@ public class JetWhenEntry extends JetElement { public void accept(@NotNull JetVisitor visitor) { visitor.visitWhenEntry(this); } + + @Nullable + public JetWhenCondition getCondition() { + return findChildByClass(JetWhenCondition.class); + } } diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetWhenExpression.java b/idea/src/org/jetbrains/jet/lang/psi/JetWhenExpression.java index 982aac07e22..ffa9289b922 100644 --- a/idea/src/org/jetbrains/jet/lang/psi/JetWhenExpression.java +++ b/idea/src/org/jetbrains/jet/lang/psi/JetWhenExpression.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; import java.util.List; @@ -19,6 +20,11 @@ public class JetWhenExpression extends JetExpression { return findChildrenByType(JetNodeTypes.WHEN_ENTRY); } + @Nullable @IfNotParsed + public JetExpression getSubjectExpression() { + return findChildByClass(JetExpression.class); + } + @Override public void accept(JetVisitor visitor) { visitor.visitWhenExpression(this); diff --git a/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java b/idea/src/org/jetbrains/jet/lang/types/JetTypeInferrer.java index 9718dad43cb..4f001065bc2 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.google.common.collect.Sets; import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; @@ -151,7 +152,7 @@ public class JetTypeInferrer { public void visitQualifiedExpression(JetQualifiedExpression expression) { // . or ?. JetType receiverType = getType(scope, expression.getReceiverExpression(), false); - checkNullSafety(receiverType, expression); + checkNullSafety(receiverType, expression.getOperationTokenNode()); JetExpression selectorExpression = expression.getSelectorExpression(); if (selectorExpression instanceof JetSimpleNameExpression) { @@ -191,19 +192,20 @@ public class JetTypeInferrer { return wrapForTracing(result[0], reference[0], argumentList, true); } - private void checkNullSafety(JetType receiverType, JetQualifiedExpression expression) { + private void checkNullSafety(JetType receiverType, ASTNode operationTokenNode) { if (receiverType != null) { boolean namespaceType = receiverType instanceof NamespaceType; boolean nullable = !namespaceType && receiverType.isNullable(); - if (nullable && expression.getOperationSign() == JetTokens.DOT) { - trace.getErrorHandler().genericError(expression.getOperationTokenNode(), "Only safe calls (?.) are allowed on a nullable receiver of type " + receiverType); + IElementType operationSign = operationTokenNode.getElementType(); + if (nullable && operationSign == JetTokens.DOT) { + trace.getErrorHandler().genericError(operationTokenNode, "Only safe calls (?.) are allowed on a nullable receiver of type " + receiverType); } - else if (!nullable && expression.getOperationSign() == JetTokens.SAFE_ACCESS) { + else if (!nullable && operationSign == JetTokens.SAFE_ACCESS) { if (namespaceType) { - trace.getErrorHandler().genericError(expression.getOperationTokenNode(), "Safe calls are not allowed on namespaces"); + trace.getErrorHandler().genericError(operationTokenNode, "Safe calls are not allowed on namespaces"); } else { - trace.getErrorHandler().genericWarning(expression.getOperationTokenNode(), "Unnecessary safe call on a non-null receiver of type " + receiverType); + trace.getErrorHandler().genericWarning(operationTokenNode, "Unnecessary safe call on a non-null receiver of type " + receiverType); } } } @@ -378,7 +380,10 @@ public class JetTypeInferrer { } else { JetExpression resultExpression = entry.getExpression(); if (resultExpression != null) { - result.add(getType(scope, resultExpression, true)); + JetType type = getType(scope, resultExpression, true); + if (type != null) { + result.add(type); + } } } } @@ -964,11 +969,75 @@ public class JetTypeInferrer { } @Override - public void visitWhenExpression(JetWhenExpression expression) { + public void visitWhenExpression(final JetWhenExpression expression) { // TODO :change scope according to the bound value in the when header - List expressions = new ArrayList(); - collectAllReturnTypes(expression, scope, expressions); - result = semanticServices.getTypeChecker().commonSupertype(expressions); + final JetExpression subjectExpression = expression.getSubjectExpression(); + + JetType subjectType = null; + if (subjectExpression != null) { + subjectType = getType(scope, subjectExpression, false); + } + + // TODO : exhaustive patterns + + for (JetWhenEntry whenEntry : expression.getEntries()) { + final JetType finalSubjectType = subjectType; + JetWhenCondition condition = whenEntry.getCondition(); + if (condition != null) { + condition.accept(new JetVisitor() { + @Override + public void visitWhenConditionWithExpression(JetWhenConditionWithExpression condition) { + JetExpression conditionExpression = condition.getExpression(); + if (conditionExpression != null) { + JetType type = getType(scope, conditionExpression, false); + if (type != null && finalSubjectType != null) { + if (TypeUtils.intersect(semanticServices.getTypeChecker(), Sets.newHashSet(finalSubjectType, type)) == null) { + trace.getErrorHandler().genericError(conditionExpression.getNode(), "This condition can never hold"); + } + } + } + } + + @Override + public void visitWhenConditionCall(JetWhenConditionCall condition) { + checkNullSafety(finalSubjectType, condition.getOperationTokenNode()); + JetExpression callSuffixExpression = condition.getCallSuffixExpression(); + JetScope compositeScope = new ScopeWithReceiver(scope, finalSubjectType); + if (callSuffixExpression != null) { + JetType selectorReturnType = getType(compositeScope, callSuffixExpression, false); + ensureBooleanResultWithCustomSubject(callSuffixExpression, selectorReturnType, "This expression"); + } + } + + @Override + public void visitWhenConditionInRange(JetWhenConditionInRange condition) { + JetExpression rangeExpression = condition.getRangeExpression(); + if (rangeExpression != null) { + checkInExpression(condition.getOperationReference(), subjectExpression, rangeExpression); + } + } + + @Override + public void visitWhenConditionIsPattern(JetWhenConditionIsPattern condition) { + super.visitWhenConditionIsPattern(condition); // TODO + } + + @Override + public void visitJetElement(JetElement elem) { + trace.getErrorHandler().genericError(elem.getNode(), "Unsupported [JetTypeInferrer] : " + elem); + } + }); + } + } + + List expressionTypes = new ArrayList(); + collectAllReturnTypes(expression, scope, expressionTypes); + if (!expressionTypes.isEmpty()) { + result = semanticServices.getTypeChecker().commonSupertype(expressionTypes); + } + else { + trace.getErrorHandler().genericError(expression.getNode(), "Entries required for when-expression"); // TODO : Scope, and maybe this should not an error + } } @Override @@ -1226,10 +1295,10 @@ public class JetTypeInferrer { JetExpression receiverExpression = expression.getReceiverExpression(); JetType receiverType = new TypeInferrerVisitorWithNamespaces(scope, false).getType(receiverExpression); if (receiverType != null) { - checkNullSafety(receiverType, expression); + checkNullSafety(receiverType, expression.getOperationTokenNode()); JetType selectorReturnType = getSelectorReturnType(receiverType, selectorExpression); if (expression.getOperationSign() == JetTokens.QUEST) { - if (selectorReturnType != null && !isBoolean(selectorReturnType)) { + if (selectorReturnType != null && !isBoolean(selectorReturnType) && selectorExpression != null) { // TODO : more comprehensible error message trace.getErrorHandler().typeMismatch(selectorExpression, semanticServices.getStandardLibrary().getBooleanType(), selectorReturnType); } @@ -1395,9 +1464,7 @@ public class JetTypeInferrer { result = ErrorUtils.createErrorType("No right argument"); // TODO return; } - String name = "contains"; - JetType containsType = getTypeForBinaryCall(scope, right, expression.getOperationReference(), expression.getLeft(), name, true); - ensureBooleanResult(operationSign, name, containsType); + checkInExpression(operationSign, left, right); result = semanticServices.getStandardLibrary().getBooleanType(); } else if (operationType == JetTokens.ANDAND || operationType == JetTokens.OROR) { @@ -1428,6 +1495,12 @@ public class JetTypeInferrer { } } + private void checkInExpression(JetSimpleNameExpression operationSign, JetExpression left, JetExpression right) { + String name = "contains"; + JetType containsType = getTypeForBinaryCall(scope, right, operationSign, left, name, true); + ensureBooleanResult(operationSign, name, containsType); + } + private void ensureNonemptyIntersectionOfOperandTypes(JetBinaryExpression expression) { JetSimpleNameExpression operationSign = expression.getOperationReference(); JetExpression left = expression.getLeft(); @@ -1459,11 +1532,15 @@ public class JetTypeInferrer { trace.getErrorHandler().genericError(expression.getNode(), "Assignments are not expressions, and only expressions are allowed in this context"); } - private boolean ensureBooleanResult(JetSimpleNameExpression operationSign, String name, JetType resultType) { + private boolean ensureBooleanResult(JetExpression operationSign, String name, JetType resultType) { + return ensureBooleanResultWithCustomSubject(operationSign, resultType, "'" + name + "'"); + } + + private boolean ensureBooleanResultWithCustomSubject(JetExpression operationSign, JetType resultType, String subjectName) { if (resultType != null) { // TODO : Relax? if (!isBoolean(resultType)) { - trace.getErrorHandler().genericError(operationSign.getNode(), "'" + name + "' must return Boolean but returns " + resultType); + trace.getErrorHandler().genericError(operationSign.getNode(), subjectName + " must return Boolean but returns " + resultType); return false; } } diff --git a/idea/testData/checker/When.jet b/idea/testData/checker/When.jet new file mode 100644 index 00000000000..3fa319c383d --- /dev/null +++ b/idea/testData/checker/When.jet @@ -0,0 +1,18 @@ +fun foo() { + val s = "" + val x = 1 + when (x) { + s => 1 + 1 => 1 + 1 + a => 1 + in 1..a => 1 + !in 1..a => 1 + .a => 1 + .equals(1).a => 1 + ?.equals(1) => 1 + } + when (x?:null) { + .equals(1) => 1 + ?.equals(1).equals(2) => 1 + } +} \ No newline at end of file diff --git a/idea/testData/psi/NewlinesInParentheses.txt b/idea/testData/psi/NewlinesInParentheses.txt index 37b3cae5c5e..94d41052376 100644 --- a/idea/testData/psi/NewlinesInParentheses.txt +++ b/idea/testData/psi/NewlinesInParentheses.txt @@ -254,7 +254,8 @@ JetFile: NewlinesInParentheses.jet PsiWhiteSpace('\n ') WHEN_ENTRY WHEN_CONDITION_IN_RANGE - PsiElement(in)('in') + OPERATION_REFERENCE + PsiElement(in)('in') PsiWhiteSpace(' ') CALL_EXPRESSION REFERENCE_EXPRESSION @@ -290,7 +291,8 @@ JetFile: NewlinesInParentheses.jet PsiWhiteSpace('\n ') WHEN_ENTRY WHEN_CONDITION_IN_RANGE - PsiElement(NOT_IN)('!in') + OPERATION_REFERENCE + PsiElement(NOT_IN)('!in') PsiWhiteSpace(' ') CALL_EXPRESSION REFERENCE_EXPRESSION diff --git a/idea/testData/psi/When.txt b/idea/testData/psi/When.txt index 27d4df20d49..0c5f8311f32 100644 --- a/idea/testData/psi/When.txt +++ b/idea/testData/psi/When.txt @@ -755,7 +755,8 @@ JetFile: When.jet PsiWhiteSpace('\n ') WHEN_ENTRY WHEN_CONDITION_IN_RANGE - PsiElement(in)('in') + OPERATION_REFERENCE + PsiElement(in)('in') PsiWhiteSpace(' ') BINARY_EXPRESSION INTEGER_CONSTANT @@ -772,7 +773,8 @@ JetFile: When.jet PsiWhiteSpace('\n ') WHEN_ENTRY WHEN_CONDITION_IN_RANGE - PsiElement(NOT_IN)('!in') + OPERATION_REFERENCE + PsiElement(NOT_IN)('!in') PsiWhiteSpace(' ') INTEGER_CONSTANT PsiElement(INTEGER_LITERAL)('2') diff --git a/idea/testData/psi/When_ERR.txt b/idea/testData/psi/When_ERR.txt index 110e6bb8097..2fc8995909f 100644 --- a/idea/testData/psi/When_ERR.txt +++ b/idea/testData/psi/When_ERR.txt @@ -59,7 +59,8 @@ JetFile: When_ERR.jet PsiWhiteSpace('\n ') WHEN_ENTRY WHEN_CONDITION_IN_RANGE - PsiElement(in)('in') + OPERATION_REFERENCE + PsiElement(in)('in') PsiErrorElement:Expecting an element PsiWhiteSpace(' ') @@ -70,7 +71,8 @@ JetFile: When_ERR.jet PsiWhiteSpace('\n ') WHEN_ENTRY WHEN_CONDITION_IN_RANGE - PsiElement(NOT_IN)('!in') + OPERATION_REFERENCE + PsiElement(NOT_IN)('!in') PsiErrorElement:Expecting an element PsiWhiteSpace(' ') @@ -127,7 +129,8 @@ JetFile: When_ERR.jet PsiWhiteSpace('\n ') WHEN_ENTRY WHEN_CONDITION_IN_RANGE - PsiElement(in)('in') + OPERATION_REFERENCE + PsiElement(in)('in') PsiErrorElement:Expecting an element PsiWhiteSpace(' ') @@ -137,7 +140,8 @@ JetFile: When_ERR.jet PsiWhiteSpace('\n ') WHEN_ENTRY WHEN_CONDITION_IN_RANGE - PsiElement(NOT_IN)('!in') + OPERATION_REFERENCE + PsiElement(NOT_IN)('!in') PsiErrorElement:Expecting an element PsiWhiteSpace(' ') @@ -147,7 +151,8 @@ JetFile: When_ERR.jet PsiWhiteSpace('\n ') WHEN_ENTRY WHEN_CONDITION_IN_RANGE - PsiElement(NOT_IN)('!in') + OPERATION_REFERENCE + PsiElement(NOT_IN)('!in') PsiErrorElement:Expecting an element PsiWhiteSpace(' ')