diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ReadOnlyUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ReadOnlyUtils.java index 819cca998c51..9b46eba3d30c 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ReadOnlyUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ReadOnlyUtils.java @@ -224,8 +224,8 @@ private static BIntersectionType setImmutableIntersectionType(Type type, Set entry : originalFields.entrySet()) { Field originalField = entry.getValue(); fields.put(entry.getKey(), - new BField(getImmutableType(originalField.getFieldType(), unresolvedTypes), - originalField.getFieldName(), originalField.getFlags())); + new BField(getImmutableType(originalField.getFieldType(), unresolvedTypes), + originalField.getFieldName(), originalField.getFlags() | SymbolFlags.READONLY)); } BRecordType immutableRecordType = new BRecordType( diff --git a/bvm/ballerina-runtime/src/main/java/module-info.java b/bvm/ballerina-runtime/src/main/java/module-info.java index de8f45222601..32a43bd94043 100644 --- a/bvm/ballerina-runtime/src/main/java/module-info.java +++ b/bvm/ballerina-runtime/src/main/java/module-info.java @@ -1,4 +1,5 @@ module io.ballerina.runtime { + uses io.ballerina.runtime.api.launch.LaunchListener; requires java.xml; requires org.apache.commons.text; requires axiom.api; @@ -44,23 +45,22 @@ io.ballerina.lang.floatingpoint, io.ballerina.lang.internal, io.ballerina.lang.function, io.ballerina.lang.regexp, io.ballerina.runtime.profiler; exports io.ballerina.runtime.internal.commons to io.ballerina.lang.value; - exports io.ballerina.runtime.internal.launch to io.ballerina.testerina.runtime, io.ballerina.packerina, - ballerina.test.listener, io.ballerina.cli, org.ballerinalang.debugadapter.runtime; - exports io.ballerina.runtime.internal.scheduling to io.ballerina.cli.utils, io.ballerina.java, + exports io.ballerina.runtime.internal.launch to io.ballerina.testerina.runtime, ballerina.test.listener, + io.ballerina.cli, org.ballerinalang.debugadapter.runtime; + exports io.ballerina.runtime.internal.scheduling to io.ballerina.java, io.ballerina.lang.array, io.ballerina.lang.error, io.ballerina.lang.internal, io.ballerina.lang.map, io.ballerina.lang.table, io.ballerina.lang.transaction, io.ballerina.lang.value, io.ballerina.lang.xml, - io.ballerina.log.api, io.ballerina.testerina.core, io.ballerina.testerina.runtime, io.ballerina.shell, + io.ballerina.testerina.core, io.ballerina.testerina.runtime, io.ballerina.shell, org.ballerinalang.debugadapter.runtime, io.ballerina.lang.function, io.ballerina.runtime.profiler; exports io.ballerina.runtime.internal.util to io.ballerina.testerina.runtime, io.ballerina.lang, io.ballerina.lang.integer, io.ballerina.lang.floatingpoint, io.ballerina.lang.array, io.ballerina.lang.table, io.ballerina.java, io.ballerina.lang.map, io.ballerina.lang.string, io.ballerina.lang.xml, io.ballerina.lang.bool, io.ballerina.lang.error, io.ballerina.lang.internal, - io.ballerina.lang.value, io.ballerina.auth, io.ballerina.runtime.api, io.ballerina.cli.utils, - io.ballerina.cli; + io.ballerina.lang.value, io.ballerina.cli; exports io.ballerina.runtime.internal.errors to io.ballerina.lang.value, io.ballerina.lang.integer, io.ballerina.java, io.ballerina.lang.internal, io.ballerina.lang.array, io.ballerina.lang.bool, io.ballerina.lang.floatingpoint, io.ballerina.lang.map, io.ballerina.lang.string, io.ballerina.lang.table, - io.ballerina.lang.xml, io.ballerina.testerina.core, io.ballerina.cli.utils, io.ballerina.cli, + io.ballerina.lang.xml, io.ballerina.testerina.core, io.ballerina.cli, io.ballerina.lang.decimal, org.ballerinalang.debugadapter.runtime, io.ballerina.lang.function, io.ballerina.lang.regexp; exports io.ballerina.runtime.internal.values to io.ballerina.testerina.core, io.ballerina.testerina.runtime, diff --git a/compiler/ballerina-lang/src/main/java/module-info.java b/compiler/ballerina-lang/src/main/java/module-info.java index f3ae030b12a9..bf773acd7296 100644 --- a/compiler/ballerina-lang/src/main/java/module-info.java +++ b/compiler/ballerina-lang/src/main/java/module-info.java @@ -1,6 +1,9 @@ module io.ballerina.lang { uses io.ballerina.projects.plugins.CompilerPlugin; uses io.ballerina.projects.buildtools.CodeGeneratorTool; + uses org.ballerinalang.spi.CompilerBackendCodeGenerator; + uses org.ballerinalang.compiler.plugins.CompilerPlugin; + uses org.ballerinalang.spi.EmbeddedExecutor; requires java.compiler; requires com.google.gson; requires java.xml; diff --git a/distribution/zip/jballerina-tools/build.gradle b/distribution/zip/jballerina-tools/build.gradle index c88276e683e0..f0f2c4d4c83b 100644 --- a/distribution/zip/jballerina-tools/build.gradle +++ b/distribution/zip/jballerina-tools/build.gradle @@ -389,7 +389,7 @@ tasks.register('pathingJar', Jar) { doFirst { manifest { attributes "Class-Path": configurations.docerina.files.collect { - it.toURL().toString().replaceFirst("file:/", "/") + it.toURI().toString() }.join(' ') } } diff --git a/language-server/modules/langserver-core/src/main/java/module-info.java b/language-server/modules/langserver-core/src/main/java/module-info.java index deac82e07835..a2ad981981ed 100644 --- a/language-server/modules/langserver-core/src/main/java/module-info.java +++ b/language-server/modules/langserver-core/src/main/java/module-info.java @@ -11,6 +11,7 @@ uses org.ballerinalang.langserver.eventsync.EventPublisher; uses org.ballerinalang.langserver.commons.codeaction.spi.DiagnosticBasedCodeActionProvider; uses org.ballerinalang.langserver.commons.codeaction.spi.RangeBasedCodeActionProvider; + uses org.ballerinalang.langserver.commons.completion.spi.BallerinaCompletionProvider; exports org.ballerinalang.langserver; exports org.ballerinalang.langserver.references; exports org.ballerinalang.langserver.common.utils; @@ -20,7 +21,7 @@ exports org.ballerinalang.langserver.extensions; exports org.ballerinalang.langserver.config; exports org.ballerinalang.langserver.telemetry; - exports org.ballerinalang.langserver.util to io.ballerina.language.server.simulator; + exports org.ballerinalang.langserver.util; exports org.ballerinalang.langserver.codeaction to io.ballerina.datamapper; requires io.ballerina.diagram.util; requires io.ballerina.formatter.core; 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 b9ca6102648a..9a2c6b1f7a23 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 @@ -38,6 +38,8 @@ public class MappingConstructorExprTest { private CompileResult inferRecordResult; private CompileResult spreadOpFieldResult; private CompileResult readOnlyFieldResult; + private CompileResult resultWithTupleUpdateMethod; + private CompileResult resultWithoutTupleUpdateMethod; @BeforeClass public void setup() { @@ -47,6 +49,10 @@ public void setup() { varNameFieldResult = BCompileUtil.compile("test-src/expressions/mappingconstructor/var_name_field.bal"); spreadOpFieldResult = BCompileUtil.compile("test-src/expressions/mappingconstructor/spread_op_field.bal"); readOnlyFieldResult = BCompileUtil.compile("test-src/expressions/mappingconstructor/readonly_field.bal"); + resultWithTupleUpdateMethod = + BCompileUtil.compile("test-src/types/readonly/test_tuple_vs_array_readonly_violation_consistency.bal"); + resultWithoutTupleUpdateMethod = BCompileUtil.compile("test-src/types/readonly/" + + "test_tuple_vs_array_readonly_violation_without_tuple_update_method.bal"); } @Test(dataProvider = "mappingConstructorTests") @@ -379,6 +385,16 @@ public void testReadOnlyFields(String test) { BRunUtil.invoke(readOnlyFieldResult, test); } + @Test + public void testReadOnlyFields2() { + BRunUtil.invoke(resultWithTupleUpdateMethod, "testFrozenAnyArrayElementUpdate"); + } + + @Test + public void testReadOnlyFields3() { + BRunUtil.invoke(resultWithoutTupleUpdateMethod, "testFrozenAnyArrayElementUpdate"); + } + @DataProvider(name = "readOnlyFieldTests") public Object[][] readOnlyFieldTests() { return new Object[][] { @@ -502,5 +518,7 @@ public void tearDown() { inferRecordResult = null; spreadOpFieldResult = null; readOnlyFieldResult = null; + resultWithTupleUpdateMethod = null; + resultWithoutTupleUpdateMethod = null; } } diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/javainterop/basic/StaticMethodTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/javainterop/basic/StaticMethodTest.java index 4b56dddbe92e..539cabb346a2 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/javainterop/basic/StaticMethodTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/javainterop/basic/StaticMethodTest.java @@ -169,8 +169,8 @@ public void testCreateRawDetails() { } @Test(expectedExceptions = BLangTestException.class, - expectedExceptionsMessageRegExp = ".*Invalid update of record field: modification not allowed on readonly " + - "value.*") + expectedExceptionsMessageRegExp = "error: \\{ballerina/lang.map}InherentTypeViolation \\{\"message\":" + + "\"cannot update 'readonly' field 'name' in record of type 'Details & readonly'\".*") public void testCreateDetails() { BRunUtil.invoke(result, "testCreateDetails"); } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/annotations/annotation_access_negative.bal b/tests/jballerina-unit-test/src/test/resources/test-src/annotations/annotation_access_negative.bal index 462145a9f1c9..c3fa341d4c0b 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/annotations/annotation_access_negative.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/annotations/annotation_access_negative.bal @@ -47,8 +47,8 @@ function testRecordTypeAnnotationReadonlyValueEdit() { assertEquality(true, res is error); error resError = res; - assertEquality("{ballerina/lang.map}InvalidUpdate", resError.message()); - assertEquality("Invalid update of record field: modification not allowed on readonly value", + assertEquality("{ballerina/lang.map}InherentTypeViolation", resError.message()); + assertEquality("cannot update 'readonly' field 'foo' in record of type 'Annot & readonly'", resError.detail()["message"]); } @@ -73,8 +73,8 @@ function testAnnotationOnObjectTypeReadonlyValueEdit() { assertEquality(true, res is error); error resError = res; - assertEquality("{ballerina/lang.map}InvalidUpdate", resError.message()); - assertEquality("Invalid update of record field: modification not allowed on readonly value", + assertEquality("{ballerina/lang.map}InherentTypeViolation", resError.message()); + assertEquality("cannot update 'readonly' field 'foo' in record of type 'Annot & readonly'", resError.detail()["message"]); } @@ -97,8 +97,8 @@ function testAnnotationOnFunctionTypeReadonlyValueEdit() { assertTrue(res is error); error resError = res; - assertEquality("{ballerina/lang.map}InvalidUpdate", resError.message()); - assertEquality("Invalid update of record field: modification not allowed on readonly value", + assertEquality("{ballerina/lang.map}InherentTypeViolation", resError.message()); + assertEquality("cannot update 'readonly' field 'foo' in record of type 'Annot & readonly'", resError.detail()["message"]); } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_tuple_vs_array_readonly_violation_consistency.bal b/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_tuple_vs_array_readonly_violation_consistency.bal new file mode 100644 index 000000000000..ed4ca4cfc3fe --- /dev/null +++ b/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_tuple_vs_array_readonly_violation_consistency.bal @@ -0,0 +1,48 @@ +// Copyright (c) 2024, WSO2 LLC. (http://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 ballerina/test; + +type Employee record {| + int id; + string name; +|}; + +function testFrozenAnyArrayElementUpdate() returns error? { + error? actualError = trap frozenAnyArrayElementUpdate(); + test:assertTrue(actualError is error); + error e = actualError; + test:assertEquals(e.message(), "{ballerina/lang.map}InherentTypeViolation"); + test:assertEquals(e.detail()["message"], + "cannot update 'readonly' field 'name' in record of type '(Employee & readonly)'"); +} + +function frozenAnyArrayElementUpdate() returns error? { + Employee e1 = {name: "Em", id: 1000}; + anydata[] i1 = [e1]; + anydata[] i2 = i1.cloneReadOnly(); + Employee e2 = check trap i2[0]; + e2["name"] = "Zee"; + return (); +} + +function frozenTupleUpdate() { + Employee e1 = {name: "Em", id: 1000}; + [int, Employee] t1 = [1, e1]; + [int, Employee] t2 = t1.cloneReadOnly(); + Employee e2 = {name: "Zee", id: 1200}; + t2[1] = e2; +} diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_tuple_vs_array_readonly_violation_without_tuple_update_method.bal b/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_tuple_vs_array_readonly_violation_without_tuple_update_method.bal new file mode 100644 index 000000000000..b04fe3c9e75a --- /dev/null +++ b/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_tuple_vs_array_readonly_violation_without_tuple_update_method.bal @@ -0,0 +1,38 @@ +// Copyright (c) 2024, WSO2 LLC. (http://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 ballerina/test; + +type Employee record {| + int id; + string name; +|}; + +function frozenAnyArrayElementUpdate() returns error? { + Employee e1 = {name: "Em", id: 1000}; + anydata[] i1 = [e1]; + anydata[] i2 = i1.cloneReadOnly(); + Employee e2 = check trap i2[0]; + e2["name"] = "Zee"; + return (); +} + +function testFrozenAnyArrayElementUpdate() returns error? { + error? actualError = trap frozenAnyArrayElementUpdate(); + test:assertTrue(actualError is error); + test:assertEquals((actualError).message(), "{ballerina/lang.map}InherentTypeViolation"); + test:assertEquals((actualError).detail()["message"], + "cannot update 'readonly' field 'name' in record of type 'Employee & readonly'"); +}