Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate expr with check keyword in call stmt #29282

Merged
merged 9 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ public enum DiagnosticErrorCode implements DiagnosticCode {

// Checked expression related errors
CHECKED_EXPR_INVALID_USAGE_NO_ERROR_TYPE_IN_RHS("BCE3030", "checked.expr.invalid.usage.no.error.type.rhs"),
CHECKED_EXPR_INVALID_USAGE_ALL_ERROR_TYPES_IN_RHS("BCE3031", "checked.expr.invalid.usage.only.error.types.rhs"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we fix the subsequent diagnostic IDs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we update this later, This might conflict with other PRs.

CHECKED_EXPR_NO_MATCHING_ERROR_RETURN_IN_ENCL_INVOKABLE(
"BCE3032", "checked.expr.no.matching.error.return.in.encl.invokable"),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4807,17 +4807,14 @@ private void visitCheckAndCheckPanicExpr(BLangCheckedExpr checkedExpr) {
}
}

if (exprType.tag != TypeTags.UNION) {
boolean isErrorType = types.isAssignable(exprType, symTable.errorType);
if (exprType.tag != TypeTags.UNION && !isErrorType) {
if (exprType.tag == TypeTags.READONLY) {
checkedExpr.equivalentErrorTypeList = new ArrayList<>(1) {{
add(symTable.errorType);
}};
resultType = symTable.anyAndReadonly;
return;
} else if (types.isAssignable(exprType, symTable.errorType)) {
dlog.error(checkedExpr.expr.pos,
DiagnosticErrorCode.CHECKED_EXPR_INVALID_USAGE_ALL_ERROR_TYPES_IN_RHS,
operatorType);
} else if (exprType != symTable.semanticError) {
dlog.error(checkedExpr.expr.pos,
DiagnosticErrorCode.CHECKED_EXPR_INVALID_USAGE_NO_ERROR_TYPE_IN_RHS,
Expand All @@ -4830,17 +4827,21 @@ private void visitCheckAndCheckPanicExpr(BLangCheckedExpr checkedExpr) {
// Filter out the list of types which are not equivalent with the error type.
List<BType> errorTypes = new ArrayList<>();
List<BType> nonErrorTypes = new ArrayList<>();
for (BType memberType : ((BUnionType) exprType).getMemberTypes()) {
if (memberType.tag == TypeTags.READONLY) {
errorTypes.add(symTable.errorType);
nonErrorTypes.add(symTable.anyAndReadonly);
continue;
}
if (types.isAssignable(memberType, symTable.errorType)) {
errorTypes.add(memberType);
continue;
if (!isErrorType) {
for (BType memberType : ((BUnionType) exprType).getMemberTypes()) {
if (memberType.tag == TypeTags.READONLY) {
errorTypes.add(symTable.errorType);
nonErrorTypes.add(symTable.anyAndReadonly);
continue;
}
if (types.isAssignable(memberType, symTable.errorType)) {
errorTypes.add(memberType);
continue;
}
nonErrorTypes.add(memberType);
}
nonErrorTypes.add(memberType);
} else {
errorTypes.add(exprType);
}

// This list will be used in the desugar phase
Expand All @@ -4853,17 +4854,10 @@ private void visitCheckAndCheckPanicExpr(BLangCheckedExpr checkedExpr) {
return;
}

if (nonErrorTypes.isEmpty()) {
// All member types in the union are equivalent to the error type.
// Checked expression requires at least one type which is not equivalent to the error type.
dlog.error(checkedExpr.expr.pos,
DiagnosticErrorCode.CHECKED_EXPR_INVALID_USAGE_ALL_ERROR_TYPES_IN_RHS, operatorType);
chiranSachintha marked this conversation as resolved.
Show resolved Hide resolved
checkedExpr.type = symTable.semanticError;
return;
}

BType actualType;
if (nonErrorTypes.size() == 1) {
if (nonErrorTypes.size() == 0) {
actualType = symTable.neverType;
} else if (nonErrorTypes.size() == 1) {
actualType = nonErrorTypes.get(0);
} else {
actualType = BUnionType.create(null, new LinkedHashSet<>(nonErrorTypes));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1073,9 +1073,6 @@ error.unsupported.builtin.method=\
error.checked.expr.invalid.usage.no.error.type.rhs=\
invalid usage of the ''{0}'' expression operator: no expression type is equivalent to error type

error.checked.expr.invalid.usage.only.error.types.rhs=\
invalid usage of the ''{0}'' expression operator: all expression types are equivalent to error type

error.checked.expr.no.matching.error.return.in.encl.invokable=\
invalid usage of the ''check'' expression operator: no matching error return type(s) in the enclosing invokable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,8 @@ public enum DiagnosticErrorCode implements DiagnosticCode {
ERROR_COMPLEX_VARIABLE_MUST_BE_INITIALIZED("BCE667", "error.complex.variable.must.be.initialized"),
ERROR_ISOLATED_VAR_CANNOT_BE_DECLARED_AS_PUBLIC("BCE668", "error.isolated.var.cannot.be.declared.as.public"),
ERROR_VARIABLE_DECLARED_WITH_VAR_CANNOT_BE_PUBLIC("BCE669", "error.variable.declared.with.var.cannot.be.public"),
ERROR_FIELD_BP_INSIDE_LIST_BP("BCE670", "error.field.binding.pattern.inside.list.binding.pattern")
ERROR_FIELD_BP_INSIDE_LIST_BP("BCE670", "error.field.binding.pattern.inside.list.binding.pattern"),
ERROR_INVALID_EXPRESSION_EXPECTED_CALL_EXPRESSION("BCE671", "error.invalid.expression.expected.a.call.expression")
;

String diagnosticId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.ballerina.compiler.internal.parser.tree.STBinaryExpressionNode;
import io.ballerina.compiler.internal.parser.tree.STBracedExpressionNode;
import io.ballerina.compiler.internal.parser.tree.STBuiltinSimpleNameReferenceNode;
import io.ballerina.compiler.internal.parser.tree.STCheckExpressionNode;
import io.ballerina.compiler.internal.parser.tree.STConditionalExpressionNode;
import io.ballerina.compiler.internal.parser.tree.STDefaultableParameterNode;
import io.ballerina.compiler.internal.parser.tree.STErrorConstructorExpressionNode;
Expand Down Expand Up @@ -8207,9 +8208,37 @@ private STNode parseCallStatement(STNode expression) {
// This is not a must because this expression is validated in the semantic analyzer.
STNode semicolon = parseSemicolon();
endContext();
if (expression.kind == SyntaxKind.CHECK_EXPRESSION) {
expression = validateCallExpression(expression);
}
return STNodeFactory.createExpressionStatementNode(SyntaxKind.CALL_STATEMENT, expression, semicolon);
}

private STNode validateCallExpression(STNode callExpr) {
STCheckExpressionNode checkExpr = (STCheckExpressionNode) callExpr;
STNode expr = checkExpr.expression;
if (expr.kind == SyntaxKind.FUNCTION_CALL || expr.kind == SyntaxKind.METHOD_CALL) {
return callExpr;
}
chiranSachintha marked this conversation as resolved.
Show resolved Hide resolved

STNode checkKeyword = checkExpr.checkKeyword;
if (expr.kind == SyntaxKind.CHECK_EXPRESSION) {
expr = validateCallExpression(expr);
return STNodeFactory.createCheckExpressionNode(SyntaxKind.CHECK_EXPRESSION, checkKeyword, expr);
}
chiranSachintha marked this conversation as resolved.
Show resolved Hide resolved

STNode checkingKeyword = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(checkKeyword, expr,
DiagnosticErrorCode.ERROR_INVALID_EXPRESSION_EXPECTED_CALL_EXPRESSION);
STNode funcName = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
funcName = STNodeFactory.createSimpleNameReferenceNode(funcName);
STNode openParenToken = SyntaxErrors.createMissingToken(SyntaxKind.OPEN_PAREN_TOKEN);
STNode arguments = STNodeFactory.createEmptyNodeList();
STNode closeParenToken = SyntaxErrors.createMissingToken(SyntaxKind.CLOSE_PAREN_TOKEN);
STNode funcCallExpr = STNodeFactory.createFunctionCallExpressionNode(funcName, openParenToken, arguments,
closeParenToken);
return STNodeFactory.createCheckExpressionNode(SyntaxKind.CHECK_EXPRESSION, checkingKeyword, funcCallExpr);
}

private STNode parseActionStatement(STNode action) {
STNode semicolon = parseSemicolon();
endContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,4 @@ error.complex.variable.must.be.initialized=complex variable must be initialized
error.isolated.var.cannot.be.declared.as.public=isolated variable cannot be public
error.variable.declared.with.var.cannot.be.public=variable declared with var cannot be public
error.field.binding.pattern.inside.list.binding.pattern=field binding pattern inside list binding pattern
error.invalid.expression.expected.a.call.expression = invalid expression, expected a call expression
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"kind": "FUNCTION_DEFINITION",
"hasDiagnostics": true,
"children": [
{
"kind": "LIST",
Expand Down Expand Up @@ -55,6 +56,7 @@
},
{
"kind": "FUNCTION_BODY_BLOCK",
"hasDiagnostics": true,
"children": [
{
"kind": "OPEN_BRACE_TOKEN",
Expand All @@ -67,12 +69,15 @@
},
{
"kind": "LIST",
"hasDiagnostics": true,
"children": [
{
"kind": "CALL_STATEMENT",
"hasDiagnostics": true,
"children": [
{
"kind": "CHECK_EXPRESSION",
"hasDiagnostics": true,
"children": [
{
"kind": "CHECK_KEYWORD",
Expand All @@ -91,6 +96,7 @@
},
{
"kind": "CHECK_EXPRESSION",
"hasDiagnostics": true,
"children": [
{
"kind": "CHECK_KEYWORD",
Expand All @@ -103,22 +109,56 @@
},
{
"kind": "CHECK_EXPRESSION",
"hasDiagnostics": true,
"children": [
{
"kind": "CHECKPANIC_KEYWORD",
"hasDiagnostics": true,
"diagnostics": [
"ERROR_INVALID_EXPRESSION_EXPECTED_CALL_EXPRESSION"
],
"trailingMinutiae": [
{
"kind": "WHITESPACE_MINUTIAE",
"value": " "
},
{
"kind": "INVALID_NODE_MINUTIAE",
"invalidNode": {
"kind": "INVALID_TOKEN_MINUTIAE_NODE",
"children": [
{
"kind": "DECIMAL_INTEGER_LITERAL_TOKEN",
"value": "4"
}
]
}
}
]
},
{
"kind": "NUMERIC_LITERAL",
"kind": "FUNCTION_CALL",
"children": [
{
"kind": "DECIMAL_INTEGER_LITERAL_TOKEN",
"value": "4"
"kind": "SIMPLE_NAME_REFERENCE",
"children": [
{
"kind": "IDENTIFIER_TOKEN",
"isMissing": true
}
]
},
{
"kind": "OPEN_PAREN_TOKEN",
"isMissing": true
},
{
"kind": "LIST",
"children": []
},
{
"kind": "CLOSE_PAREN_TOKEN",
"isMissing": true
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,14 @@ function testXMLCycleDueToChildrenOfChildren() returns xml|error {
var cds = cat.getChildren().strip();
'xml:Element fc = <'xml:Element> cds[0];
error? er = trap fc.setChildren(subRoot);
check trap fc.setChildren(subRoot);
check setChildren(fc, subRoot);
chiranSachintha marked this conversation as resolved.
Show resolved Hide resolved
return cat;
}

function setChildren('xml:Element fc, 'xml:Element subRoot) returns error? {
return trap fc.setChildren(subRoot);
}

function testGet() returns [xml|error, xml|error, xml|error, xml|error, xml|error] {
var e = 'xml:createElement("elem");
xml|error e1 = trap e.get(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,14 @@ public class CheckedExprNegativeTest {
public void testSemanticErrors() {
CompileResult compile = BCompileUtil.compile(
"test-src/expressions/checkedexpr/checked_expr_semantics_negative.bal");
Assert.assertEquals(compile.getErrorCount(), 4, compile.toString());
Assert.assertEquals(compile.getErrorCount(), 5, compile.toString());
BAssertUtil.validateError(compile, 0, "invalid usage of the 'check' expression " +
"operator: no expression type is equivalent to error type", 11, 25);
BAssertUtil.validateError(compile, 1, "invalid usage of the 'check' expression " +
"operator: all expression types are equivalent to error type", 16, 25);
BAssertUtil.validateError(compile, 2, "invalid usage of the 'check' expression " +
"operator: all expression types are equivalent to error type", 30, 25);
BAssertUtil.validateError(compile, 1, "incompatible types: expected 'string', found 'never'", 16, 19);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example is here is

function testCheckedExprSemanticErrors2() returns error? {
    string line = checkpanic readLineError();
}

The diagnostic is correct, But I think we can improve this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hasithaa Shall I fix this in a separate PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. Please create an issue, we can fix this later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created.(#29696)

BAssertUtil.validateError(compile, 2, "incompatible types: expected 'string', found 'never'", 30, 19);
BAssertUtil.validateError(compile, 3, "incompatible types: expected '(string|error)'" +
", found '(string|int)'", 39, 25);
BAssertUtil.validateError(compile, 4, "invalid expression, expected a call expression", 54, 5);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,9 @@ public void testSemanticErrorsWithResources() {
"test-src/expressions/checkedexpr/checked_expr_within_resource.bal");
Assert.assertEquals(compile.getErrorCount(), 0);
}

@Test
public void testCallExprWithCheck() {
BRunUtil.invoke(result, "testCallExprWithCheck");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,8 @@ public void testSemanticErrors() {
Assert.assertEquals(negative.getErrorCount(), 4, negative.toString());
BAssertUtil.validateError(negative, 0, "invalid usage of the 'checkpanic' expression " +
"operator: no expression type is equivalent to error type", 11, 30);
BAssertUtil.validateError(negative, 1, "invalid usage of the 'checkpanic' expression " +
"operator: all expression types are equivalent to error type", 16, 30);
BAssertUtil.validateError(negative, 2, "invalid usage of the 'checkpanic' expression " +
"operator: all expression types are equivalent to error type", 29, 30);
BAssertUtil.validateError(negative, 1, "incompatible types: expected 'string', found 'never'", 16, 19);
BAssertUtil.validateError(negative, 2, "incompatible types: expected 'string', found 'never'", 29, 19);
BAssertUtil.validateError(negative, 3, "incompatible types: expected '(string|error)'" +
", found '(string|int)'", 37, 30);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,38 @@ function testCheckedErrorsWithReadOnlyInUnion() {
assertEquality(1234, checkpanic y);
}

function callExprWithCheck() returns error? {
check readLineError2();
}

function returnNil() returns error? {
return ();
}

function callExprWithCheck2() returns error? {
check returnNil();
}

function readLineError2() returns error {
error e = error("io error");
return e;
}

function testCallExprWithCheck() {
assertTrue(callExprWithCheck() is error);
assertFalse(callExprWithCheck2() is error);
}

const ASSERTION_ERROR_REASON = "AssertionError";

function assertTrue(anydata actual) {
assertEquality(true, actual);
}

function assertFalse(anydata actual) {
assertEquality(false, actual);
}

function assertEquality(anydata expected, anydata actual) {
if expected == actual {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,8 @@ function readLineProper() returns string | MyError | CustomError {
function testCheckedExprSemanticErrors5() {
string line = check readLineProper();
}

function testCheckedExprSemanticErrors6() returns error? {
string|error line = readLineSuccess();
check line;
}