diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java index 20128d8b3396..0c89d01d0684 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java @@ -2576,7 +2576,7 @@ public BType checkMappingConstructorCompatibility(BType bType, BLangRecordLitera if (!erroredExpType) { reportIncompatibleMappingConstructorError(mappingConstructor, bType, data); } - validateSpecifiedFields(mappingConstructor, symTable.semanticError, data); + defineInferredRecordType(mappingConstructor, symTable.noType, data); return symTable.semanticError; } else if (compatibleTypes.size() != 1) { dlog.error(mappingConstructor.pos, DiagnosticErrorCode.AMBIGUOUS_TYPES, bType); @@ -7956,9 +7956,7 @@ private TypeSymbolPair checkRecordLiteralKeyExpr(BLangExpression keyExpr, boolea Name fieldName; if (computedKey) { - checkExpr(keyExpr, symTable.stringType, data); - - if (keyExpr.getBType() == symTable.semanticError) { + if (exprIncompatible(symTable.stringType, keyExpr, data)) { return new TypeSymbolPair(null, symTable.semanticError); } @@ -8021,13 +8019,9 @@ private BType getAllFieldType(BRecordType recordType) { private boolean checkValidJsonOrMapLiteralKeyExpr(BLangExpression keyExpr, boolean computedKey, AnalyzerData data) { if (computedKey) { - checkExpr(keyExpr, symTable.stringType, data); - - if (keyExpr.getBType() == symTable.semanticError) { - return false; - } - return true; - } else if (keyExpr.getKind() == NodeKind.SIMPLE_VARIABLE_REF || + return !exprIncompatible(symTable.stringType, keyExpr, data); + } + if (keyExpr.getKind() == NodeKind.SIMPLE_VARIABLE_REF || (keyExpr.getKind() == NodeKind.LITERAL && (keyExpr).getBType().tag == TypeTags.STRING)) { return true; } diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/bala/expressions/MappingConstructorExprBalaTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/bala/expressions/MappingConstructorExprBalaTest.java new file mode 100644 index 000000000000..ef5c56d3b2ba --- /dev/null +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/bala/expressions/MappingConstructorExprBalaTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.ballerinalang.test.bala.expressions; + +import org.ballerinalang.test.BAssertUtil; +import org.ballerinalang.test.BCompileUtil; +import org.ballerinalang.test.BRunUtil; +import org.ballerinalang.test.CompileResult; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Bala test cases for mapping constructor expression. + * + * @since 2201.9.0 + */ +public class MappingConstructorExprBalaTest { + + @BeforeClass + public void setup() { + BCompileUtil.compileAndCacheBala("test-src/bala/test_projects/test_project"); + } + + @Test + public void testModuleConstantsInMappingConstructor() { + CompileResult compileResult = + BCompileUtil.compile("test-src/bala/test_bala/expressions/mapping_constructor_expr.bal"); + BRunUtil.invoke(compileResult, "testModuleConstantsInMappingConstructor"); + } + + @Test + public void testMappingConstructorExprNegative() { + CompileResult negativeCompileResult = BCompileUtil.compile( + "test-src/bala/test_bala/expressions/mapping_constructor_expr_negative.bal"); + int i = 0; + BAssertUtil.validateError(negativeCompileResult, i++, + "attempt to refer to non-accessible symbol 'MAPPING_NAME'", 24, 23); + BAssertUtil.validateError(negativeCompileResult, i++, "undefined symbol 'MAPPING_NAME'", 24, 23); + BAssertUtil.validateError(negativeCompileResult, i++, + "incompatible mapping constructor expression for type 'map?'", 25, 22); + BAssertUtil.validateError(negativeCompileResult, i++, + "attempt to refer to non-accessible symbol 'MAPPING_NAME'", 25, 24); + BAssertUtil.validateError(negativeCompileResult, i++, "undefined symbol 'MAPPING_NAME'", 25, 24); + BAssertUtil.validateError(negativeCompileResult, i++, + "incompatible mapping constructor expression for type 'map?'", 26, 22); + BAssertUtil.validateError(negativeCompileResult, i++, + "attempt to refer to non-accessible symbol 'MAPPING_NAME'", 26, 24); + BAssertUtil.validateError(negativeCompileResult, i++, "undefined symbol 'MAPPING_NAME'", 26, 24); + BAssertUtil.validateError(negativeCompileResult, i++, "attempt to refer to non-accessible symbol 'MAPPING_C'", + 26, 43); + BAssertUtil.validateError(negativeCompileResult, i++, "undefined symbol 'MAPPING_C'", 26, 43); + BAssertUtil.validateError(negativeCompileResult, i++, "missing non-defaultable required record field 'name'", + 27, 14); + BAssertUtil.validateError(negativeCompileResult, i++, + "attempt to refer to non-accessible symbol 'MAPPING_NAME'", 27, 16); + BAssertUtil.validateError(negativeCompileResult, i++, "undefined symbol 'MAPPING_NAME'", 27, 16); + BAssertUtil.validateError(negativeCompileResult, i++, "attempt to refer to non-accessible symbol 'MAPPING_C'", + 27, 35); + BAssertUtil.validateError(negativeCompileResult, i++, "undefined symbol 'MAPPING_C'", 27, 35); + BAssertUtil.validateError(negativeCompileResult, i++, + "incompatible mapping constructor expression for type '(map|Rec)'", 28, 25); + BAssertUtil.validateError(negativeCompileResult, i++, + "attempt to refer to non-accessible symbol 'MAPPING_NAME'", 28, 27); + BAssertUtil.validateError(negativeCompileResult, i++, "undefined symbol 'MAPPING_NAME'", 28, 27); + BAssertUtil.validateError(negativeCompileResult, i++, "attempt to refer to non-accessible symbol 'MAPPING_C'", + 28, 46); + BAssertUtil.validateError(negativeCompileResult, i++, "undefined symbol 'MAPPING_C'", 28, 46); + Assert.assertEquals(negativeCompileResult.getErrorCount(), i); + } +} diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/mappingconstructor/MappingConstructorExprTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/mappingconstructor/MappingConstructorExprTest.java index f84a1a7fac40..b9ca6102648a 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/mappingconstructor/MappingConstructorExprTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/mappingconstructor/MappingConstructorExprTest.java @@ -70,23 +70,36 @@ public Object[][] mappingConstructorTests() { public void diagnosticsTest() { CompileResult result = BCompileUtil.compile( "test-src/expressions/mappingconstructor/mapping_constructor_negative.bal"); - Assert.assertEquals(result.getErrorCount(), 14); - validateError(result, 0, "incompatible mapping constructor expression for type '(string|Person)'", 33, 23); - validateError(result, 1, "ambiguous type '(PersonTwo|PersonThree)'", 37, 31); - validateError(result, 2, - "a type compatible with mapping constructor expressions not found in type '(int|float)'", 41, 19); - validateError(result, 3, "ambiguous type '(map|map)'", 45, 31); - validateError(result, 4, "ambiguous type '(map<(int|string)>|map<(string|boolean)>)'", 47, 46); - validateError(result, 5, "unknown type 'NoRecord'", 55, 5); - validateError(result, 6, "incompatible types: 'int' cannot be cast to 'string'", 55, 22); - validateError(result, 7, "invalid field access: 'salary' is not a required field in record 'PersonThree', use" + - " member access to access a field that may have been specified as a rest field", 55, 41); - validateError(result, 8, "undefined symbol 'c'", 55, 55); - validateError(result, 9, "unknown type 'Foo'", 59, 5); - validateError(result, 10, "incompatible types: 'string' cannot be cast to 'boolean'", 59, 17); - validateError(result, 11, "unknown type 'Foo'", 60, 5); - validateError(result, 12, "incompatible types: 'int' cannot be cast to 'boolean'", 60, 30); - validateError(result, 13, "ambiguous type '(any|map)'", 64, 22); + int index = 0; + validateError(result, index++, "incompatible mapping constructor expression for type '(string|Person)'", 33, + 23); + validateError(result, index++, "ambiguous type '(PersonTwo|PersonThree)'", 37, 31); + validateError(result, index++, + "a type compatible with mapping constructor expressions not found in type '(int|float)'", 41, 19); + validateError(result, index++, "ambiguous type '(map|map)'", 45, 31); + validateError(result, index++, "ambiguous type '(map<(int|string)>|map<(string|boolean)>)'", 47, 46); + validateError(result, index++, "unknown type 'NoRecord'", 55, 5); + validateError(result, index++, "incompatible types: 'int' cannot be cast to 'string'", 55, 22); + validateError(result, index++, + "invalid field access: 'salary' is not a required field in record 'PersonThree', use" + + " member access to access a field that may have been specified as a rest field", 55, 41); + validateError(result, index++, "undefined symbol 'c'", 55, 55); + validateError(result, index++, "unknown type 'Foo'", 59, 5); + validateError(result, index++, "incompatible types: 'string' cannot be cast to 'boolean'", 59, 17); + validateError(result, index++, "unknown type 'Foo'", 60, 5); + validateError(result, index++, "incompatible types: 'int' cannot be cast to 'boolean'", 60, 30); + validateError(result, index++, "ambiguous type '(any|map)'", 64, 22); + validateError(result, index++, "incompatible mapping constructor expression for type 'map?'", 68, + 22); + validateError(result, index++, "undefined symbol 'NAME'", 68, 24); + validateError(result, index++, "incompatible mapping constructor expression for type 'map?'", 69, + 22); + validateError(result, index++, "undefined symbol 'NAME'", 69, 24); + validateError(result, index++, "undefined symbol 'NAME'", 69, 32); + validateError(result, index++, "missing non-defaultable required record field 'name'", 70, 34); + validateError(result, index++, "undefined symbol 'NAME'", 70, 36); + validateError(result, index++, "undefined symbol 'NAME'", 70, 44); + Assert.assertEquals(result.getErrorCount(), index); } @Test @@ -394,6 +407,8 @@ public void testReadOnlyFieldsSemanticNegative() { 33, 35); validateError(compileResult, index++, "incompatible mapping constructor expression for type '(Employee|Details)'", 34, 27); + validateError(compileResult, index++, + "incompatible types: expected 'readonly', found 'Details'", 34, 64); validateError(compileResult, index++, "incompatible types: expected '(Employee & readonly)', found 'Employee'", 40, 18); validateError(compileResult, index++, "incompatible types: expected '(Details & readonly)', found 'Details'", @@ -403,6 +418,8 @@ public void testReadOnlyFieldsSemanticNegative() { validateError(compileResult, index++, "incompatible mapping constructor expression for type '(map|map<(Details|string)>)'", 55, 42); + validateError(compileResult, index++, + "incompatible types: expected 'readonly', found 'Details'", 55, 79); validateError(compileResult, index++, "incompatible types: expected '(map<(Details|string)> & readonly)', " + "found 'map<(Details|string)>'", 61, 18); @@ -414,6 +431,10 @@ public void testReadOnlyFieldsSemanticNegative() { "incompatible types: expected '(any & readonly)', found 'stream'", 77, 57); validateError(compileResult, index++, "incompatible mapping constructor expression for type '(" + "record {| future...; |}|NonReadOnlyFields)'", 78, 57); + validateError(compileResult, index++, + "incompatible types: expected 'readonly', found 'future'", 78, 67); + validateError(compileResult, index++, + "incompatible types: expected 'readonly', found 'stream'", 78, 84); validateError(compileResult, index++, "incompatible types: expected '((any|error) & readonly)', found 'future'", 81, 39); validateError(compileResult, index++, @@ -421,6 +442,10 @@ public void testReadOnlyFieldsSemanticNegative() { validateError(compileResult, index++, "incompatible mapping constructor expression for type '(map<(any|error)>|map>)'", 82, 43); + validateError(compileResult, index++, + "incompatible types: expected 'readonly', found 'future'", 82, 62); + validateError(compileResult, index++, + "incompatible types: expected 'readonly', found 'stream'", 82, 74); validateError(compileResult, index++, "incompatible types: expected 'record {| int i; anydata...; |}', found " + "'record {| readonly (Details & readonly) d1; readonly (Details & readonly) d2; " + "record {| string str; |} d3; readonly record {| string str; readonly int count; |} & readonly d4; " + diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/bala/test_bala/expressions/mapping_constructor_expr.bal b/tests/jballerina-unit-test/src/test/resources/test-src/bala/test_bala/expressions/mapping_constructor_expr.bal new file mode 100644 index 000000000000..c37106c198f0 --- /dev/null +++ b/tests/jballerina-unit-test/src/test/resources/test-src/bala/test_bala/expressions/mapping_constructor_expr.bal @@ -0,0 +1,42 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import testorg/foo; + +type Rec record { + string name; +}; + +function testModuleConstantsInMappingConstructor() { + map m1 = {[foo:MAPPING_A] : "A_VAL"}; + assertEquals("A_VAL", m1["A"]); + map m2 = {[foo:MAPPING_A] : foo:MAPPING_B}; + assertEquals("B", m2["A"]); + map? m3 = {[foo:MAPPING_A] : foo:MAPPING_B}; + assertEquals("B", m3["A"]); + map|Rec m4 = {[foo:MAPPING_A] : foo:MAPPING_B}; + assertEquals("B", m4["A"]); + Rec r1 = {name: foo:MAPPING_A}; + assertEquals("A", r1.name); + Rec? r2 = {name: foo:MAPPING_A}; + assertEquals("A", (r2).name); +} + +function assertEquals(anydata expected, anydata actual) { + if expected != actual { + panic error(string `expected ${expected.toBalString()}, found ${actual.toBalString()}`); + } +} diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/bala/test_bala/expressions/mapping_constructor_expr_negative.bal b/tests/jballerina-unit-test/src/test/resources/test-src/bala/test_bala/expressions/mapping_constructor_expr_negative.bal new file mode 100644 index 000000000000..715f91e5b7ea --- /dev/null +++ b/tests/jballerina-unit-test/src/test/resources/test-src/bala/test_bala/expressions/mapping_constructor_expr_negative.bal @@ -0,0 +1,29 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import testorg/foo; + +type Rec record { + string name; +}; + +public function testUnAccessibleModuleConstInMappingConstructor() { + map _ = {[foo:MAPPING_NAME]: "Amy"}; + map? _ = {[foo:MAPPING_NAME]: "Amy"}; + map? _ = {[foo:MAPPING_NAME]: foo:MAPPING_C}; + Rec? _ = {[foo:MAPPING_NAME]: foo:MAPPING_C}; + map|Rec _ = {[foo:MAPPING_NAME]: foo:MAPPING_C}; +} diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/bala/test_projects/test_project/mapping_constructor.bal b/tests/jballerina-unit-test/src/test/resources/test-src/bala/test_projects/test_project/mapping_constructor.bal new file mode 100644 index 000000000000..56acf30018ee --- /dev/null +++ b/tests/jballerina-unit-test/src/test/resources/test-src/bala/test_projects/test_project/mapping_constructor.bal @@ -0,0 +1,20 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +public const MAPPING_A = "A"; +public const MAPPING_B = "B"; +const MAPPING_C = "C"; +const MAPPING_NAME = "name"; diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/mappingconstructor/mapping_constructor_negative.bal b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/mappingconstructor/mapping_constructor_negative.bal index b34fccc47eea..cfa2f3197526 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/mappingconstructor/mapping_constructor_negative.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/mappingconstructor/mapping_constructor_negative.bal @@ -63,3 +63,9 @@ function testMappingConstrWithIssuesInCET() { function testAmbiguousTypeWithAny() { any|map _ = {a: 1}; } + +function testMappingConstructorWithUndefinedVars() { + map? _ = {[NAME] : "Amy"}; + map? _ = {[NAME] : NAME}; + record {|string name;|}? _ = {[NAME] : NAME}; +}