Skip to content

Commit

Permalink
Merge pull request ballerina-platform#15 from Anoukh/new-match-stmt
Browse files Browse the repository at this point in the history
Code Analyzer for new Match stmt
  • Loading branch information
Kishanthan authored Nov 2, 2018
2 parents 59e3291 + 08be258 commit 7290507
Show file tree
Hide file tree
Showing 5 changed files with 558 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
import org.wso2.ballerinalang.compiler.semantics.model.symbols.SymTag;
import org.wso2.ballerinalang.compiler.semantics.model.symbols.Symbols;
import org.wso2.ballerinalang.compiler.semantics.model.types.BArrayType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BField;
import org.wso2.ballerinalang.compiler.semantics.model.types.BMapType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BRecordType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BTupleType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BUnionType;
import org.wso2.ballerinalang.compiler.tree.BLangAction;
Expand Down Expand Up @@ -69,6 +73,7 @@
import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchExpression.BLangMatchExprPatternClause;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangRecordLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangRecordLiteral.BLangRecordKeyValue;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangRecordVarRef;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangRestArgsExpression;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangSimpleVarRef;
Expand Down Expand Up @@ -107,8 +112,8 @@
import org.wso2.ballerinalang.compiler.tree.statements.BLangIf;
import org.wso2.ballerinalang.compiler.tree.statements.BLangLock;
import org.wso2.ballerinalang.compiler.tree.statements.BLangMatch;
import org.wso2.ballerinalang.compiler.tree.statements.BLangMatch.BLangMatchStmtTypedBindingPatternClause;
import org.wso2.ballerinalang.compiler.tree.statements.BLangMatch.BLangMatchStmtStaticBindingPatternClause;
import org.wso2.ballerinalang.compiler.tree.statements.BLangMatch.BLangMatchStmtTypedBindingPatternClause;
import org.wso2.ballerinalang.compiler.tree.statements.BLangPanic;
import org.wso2.ballerinalang.compiler.tree.statements.BLangRecordDestructure;
import org.wso2.ballerinalang.compiler.tree.statements.BLangRecordVariableDef;
Expand Down Expand Up @@ -154,6 +159,8 @@
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.wso2.ballerinalang.compiler.util.Constants.MAIN_FUNCTION_NAME;

Expand Down Expand Up @@ -520,12 +527,178 @@ public void visit(BLangMatch matchStmt) {
}

private void analyzeStaticMatchPatterns(BLangMatch matchStmt) {
matchStmt.getStaticPatternClauses()
.stream()
.filter(pattern -> matchStmt.exprTypes
.stream()
.noneMatch(exprType -> this.types.isAssignable(exprType, pattern.literal.type)))
.forEach(pattern -> dlog.error(pattern.pos, DiagnosticCode.MATCH_STMT_UNMATCHED_PATTERN));
if (matchStmt.exprTypes.isEmpty()) {
return;
}
List<BLangExpression> matchedSimplePatterns = new ArrayList<>();
List<BLangExpression> matchedRecordPatterns = new ArrayList<>();
List<BLangExpression> matchedTuplePatterns = new ArrayList<>();
for (BLangMatchStmtStaticBindingPatternClause pattern : matchStmt.getStaticPatternClauses()) {
List<BType> matchedExpTypes = matchStmt.exprTypes
.stream()
.filter(exprType -> isValidMatchPattern(exprType, pattern.literal))
.collect(Collectors.toList());
if (matchedExpTypes.isEmpty()) {
// log error if a pattern will not match to any of the expected types
dlog.error(pattern.pos, DiagnosticCode.MATCH_STMT_UNMATCHED_PATTERN);
continue;
}

if (pattern.getLiteral().type.tag == TypeTags.MAP) {
matchedRecordPatterns.add(pattern.getLiteral());
continue;
}

if (pattern.getLiteral().type.tag == TypeTags.TUPLE) {
matchedTuplePatterns.add(pattern.getLiteral());
continue;
}

matchedSimplePatterns.add(pattern.getLiteral());
}
analyzeUnreachableStaticPatterns(matchedSimplePatterns);
analyzeUnreachableStaticPatterns(matchedTuplePatterns);
analyzeUnreachableStaticPatterns(matchedRecordPatterns);
}

private void analyzeUnreachableStaticPatterns(List<BLangExpression> matchedPatterns) {
for (int i = 0; i < matchedPatterns.size() - 1; i++) {
BLangExpression precedingPattern = matchedPatterns.get(i);
for (int j = i + 1; j < matchedPatterns.size(); j++) {
BLangExpression pattern = matchedPatterns.get(j);
if (checkLiteralSimilarity(precedingPattern, pattern)) {
dlog.error(pattern.pos, DiagnosticCode.MATCH_STMT_UNREACHABLE_PATTERN);
matchedPatterns.remove(j--);
}
}
}
}

private boolean checkLiteralSimilarity(BLangExpression precedingPattern, BLangExpression pattern) {
if (precedingPattern.type.tag == TypeTags.MAP && pattern.type.tag == TypeTags.MAP) {
BLangRecordLiteral precedingRecordLiteral = (BLangRecordLiteral) precedingPattern;
Map<String, BLangExpression> recordLiteral = ((BLangRecordLiteral) pattern).keyValuePairs
.stream()
.collect(Collectors.toMap(
keyValuePair -> ((BLangSimpleVarRef) keyValuePair.key.expr).variableName.value,
BLangRecordKeyValue::getValue
));

for (int i = 0; i < precedingRecordLiteral.keyValuePairs.size(); i++) {
BLangRecordKeyValue bLangRecordKeyValue = precedingRecordLiteral.keyValuePairs.get(i);
String key = ((BLangSimpleVarRef) bLangRecordKeyValue.key.expr).variableName.value;
if (!recordLiteral.containsKey(key)) {
return false;
}
if (!checkLiteralSimilarity(bLangRecordKeyValue.valueExpr, recordLiteral.get(key))) {
return false;
}
}
return true;
}

if (precedingPattern.type.tag == TypeTags.TUPLE && pattern.type.tag == TypeTags.TUPLE) {
BLangBracedOrTupleExpr precedingTupleLiteral = (BLangBracedOrTupleExpr) precedingPattern;
BLangBracedOrTupleExpr tupleLiteral = (BLangBracedOrTupleExpr) pattern;
for (int i = 0; i < precedingTupleLiteral.expressions.size(); i++) {
if (!checkLiteralSimilarity(precedingTupleLiteral.expressions.get(i),
tupleLiteral.expressions.get(i))) {
return false;
}
}
return true;
}

if (types.isValueType(precedingPattern.type) && types.isValueType(pattern.type)) {
BLangLiteral literal = pattern.getKind() == NodeKind.BRACED_TUPLE_EXPR ?
(BLangLiteral) ((BLangBracedOrTupleExpr) pattern).expressions.get(0) : (BLangLiteral) pattern;

BLangLiteral precedingLiteral = precedingPattern.getKind() == NodeKind.BRACED_TUPLE_EXPR ?
(BLangLiteral) ((BLangBracedOrTupleExpr) precedingPattern).expressions.get(0) :
(BLangLiteral) precedingPattern;

return (precedingLiteral.value.equals(literal.value));
}

return false;
}

private boolean isValidMatchPattern(BType matchType, BLangExpression literal) {
// note: literalType can only be simple type, map type & tuple type
if (matchType.tag == TypeTags.ARRAY) {
// TODO: Support array type static match to array literal
return false;
}

if (matchType.tag == TypeTags.ANY || matchType.tag == TypeTags.JSON) {
// when matching any type or json type, all patterns are allowed
// TODO: 11/2/18 Change to Anydata and fail any
return true;
}

if (matchType.tag == TypeTags.UNION) {
// check if at least one member in union type matches the literal type
BUnionType unionMatchType = (BUnionType) matchType;
return unionMatchType.memberTypes
.stream()
.anyMatch(memberMatchType -> isValidMatchPattern(memberMatchType, literal));
}

if (literal.type.tag == TypeTags.TUPLE && matchType.tag == TypeTags.TUPLE) {
BLangBracedOrTupleExpr tupleLiteral = (BLangBracedOrTupleExpr) literal;
BTupleType literalTupleType = (BTupleType) literal.type;
BTupleType matchTupleType = (BTupleType) matchType;

if (literalTupleType.tupleTypes.size() != matchTupleType.tupleTypes.size()) {
return false;
}

return IntStream.range(0, literalTupleType.tupleTypes.size())
.allMatch(i ->
isValidMatchPattern(matchTupleType.tupleTypes.get(i), tupleLiteral.expressions.get(i)));
}

if (literal.type.tag == TypeTags.MAP && matchType.tag == TypeTags.MAP) {
// if match type is map, check if literals match to the constraint
BLangRecordLiteral mapLiteral = (BLangRecordLiteral) literal;
return IntStream.range(0, mapLiteral.keyValuePairs.size())
.allMatch(i -> isValidMatchPattern(((BMapType) matchType).constraint,
mapLiteral.keyValuePairs.get(i).valueExpr));
}

if (literal.type.tag == TypeTags.MAP && matchType.tag == TypeTags.RECORD) {
// if match type is record, the fields must match to the static pattern fields
BLangRecordLiteral mapLiteral = (BLangRecordLiteral) literal;
BRecordType recordMatchType = (BRecordType) matchType;
Map<String, BType> recordFields = recordMatchType.fields
.stream()
.collect(Collectors.toMap(
field -> field.getName().getValue(),
BField::getType
));

for (BLangRecordKeyValue literalKeyValue : mapLiteral.keyValuePairs) {
if (recordFields.containsKey(((BLangSimpleVarRef) literalKeyValue.key.expr).variableName.value)) {
if (!isValidMatchPattern(
recordFields.get(((BLangSimpleVarRef) literalKeyValue.key.expr).variableName.value),
literalKeyValue.valueExpr)) {
return false;
}
} else if (recordMatchType.sealed) {
return false;
} else if (!isValidMatchPattern(recordMatchType.restFieldType, literalKeyValue.valueExpr)) {
return false;
}
}

return true;
}

if (matchType.tag == TypeTags.BYTE && literal.type.tag == TypeTags.INT) {
return true;
}

return types.isSameType(literal.type, matchType);
}

private void analyzeTypeMatchPatterns(BLangMatch matchStmt) {
Expand Down Expand Up @@ -871,13 +1044,13 @@ public void visit(BLangArrayLiteral arrayLiteral) {
}

public void visit(BLangRecordLiteral recordLiteral) {
List<BLangRecordLiteral.BLangRecordKeyValue> keyValuePairs = recordLiteral.keyValuePairs;
List<BLangRecordKeyValue> keyValuePairs = recordLiteral.keyValuePairs;
keyValuePairs.forEach(kv -> {
analyzeExpr(kv.valueExpr);
});

Set<Object> names = new TreeSet<>((l, r) -> l.equals(r) ? 0 : 1);
for (BLangRecordLiteral.BLangRecordKeyValue recFieldDecl : keyValuePairs) {
for (BLangRecordKeyValue recFieldDecl : keyValuePairs) {
BLangExpression key = recFieldDecl.getKey();
if (key.getKind() == NodeKind.SIMPLE_VARIABLE_REF) {
BLangSimpleVarRef keyRef = (BLangSimpleVarRef) key;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ public void visit(BLangMatchStmtStaticBindingPatternClause patternClause) {
NodeKind literalNode = patternClause.literal.getKind();

if (!(NodeKind.LITERAL == literalNode || NodeKind.RECORD_LITERAL_EXPR == literalNode ||
NodeKind.BRACED_TUPLE_EXPR == literalNode)){
NodeKind.BRACED_TUPLE_EXPR == literalNode)) {
dlog.error(patternClause.pos, INVALID_LITERAL_FOR_MATCH_PATTERN);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package org.ballerinalang.test.statements.matchstmt;

import org.ballerinalang.launcher.util.BAssertUtil;
import org.ballerinalang.launcher.util.BCompileUtil;
import org.ballerinalang.launcher.util.BRunUtil;
import org.ballerinalang.launcher.util.CompileResult;
Expand All @@ -29,15 +30,18 @@
/**
* Test cases to verify the behaviour of the static/constant value patterns with match statement in Ballerina.
*
* @since 0.983.0
* @since 0.985.0
*/
public class MatchStatementStaticPatternsTest {

private CompileResult result;
private CompileResult result, resultNegative, resultNegative2;

@BeforeClass
public void setup() {
result = BCompileUtil.compile("test-src/statements/matchstmt/static-match-patterns.bal");
resultNegative = BCompileUtil.compile("test-src/statements/matchstmt/static_match_patterns_negative.bal");
resultNegative2 = BCompileUtil.
compile("test-src/statements/matchstmt/unreachable_static_match_patterns_negative.bal");
}

@Test(description = "Test basics of static pattern match statement 1")
Expand Down Expand Up @@ -79,6 +83,84 @@ public void testMatchStatementBasics2() {
Assert.assertEquals(results.get(++i), msg + "'true'");
}

@Test(description = "Test pattern will not be matched")
public void testPatternNotMatched() {
Assert.assertEquals(resultNegative.getErrorCount(), 51);
int i = -1;
String patternNotMatched = "pattern will not be matched";

}
// simpleTypes
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 21, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 22, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 24, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 25, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 26, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 31, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 32, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 34, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 35, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 36, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 41, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 42, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 43, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 44, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 45, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 51, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 52, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 53, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 54, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 56, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 62, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 63, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 65, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 66, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 67, 9);

// recordTypes
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 109, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 111, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 113, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 114, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 115, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 116, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 124, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 130, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 132, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 141, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 150, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 157, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 159, 9);

// tupleTypes
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 168, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 169, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 170, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 171, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 172, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 174, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 175, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 190, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 196, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 203, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 206, 9);

BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 223, 9);
BAssertUtil.validateError(resultNegative, ++i, patternNotMatched, 224, 9);
}
@Test(description = "Test unreachable pattern")
public void testUnreachablePatterns() {
Assert.assertEquals(resultNegative2.getErrorCount(), 8);
int i = -1;
String unreachablePatterm =
"unreachable pattern: preceding patterns are too general or the pattern ordering is not correct";

BAssertUtil.validateError(resultNegative2, ++i, unreachablePatterm, 25, 9);
BAssertUtil.validateError(resultNegative2, ++i, unreachablePatterm, 26, 9);
BAssertUtil.validateError(resultNegative2, ++i, unreachablePatterm, 31, 9);
BAssertUtil.validateError(resultNegative2, ++i, unreachablePatterm, 41, 9);
BAssertUtil.validateError(resultNegative2, ++i, unreachablePatterm, 43, 9);
BAssertUtil.validateError(resultNegative2, ++i, unreachablePatterm, 55, 9);
BAssertUtil.validateError(resultNegative2, ++i, unreachablePatterm, 56, 9);
BAssertUtil.validateError(resultNegative2, ++i, unreachablePatterm, 57, 9);
}
}
Loading

0 comments on commit 7290507

Please sign in to comment.