diff --git a/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java b/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java index 4ae134577902..63b9648cd3bd 100644 --- a/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java +++ b/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java @@ -732,7 +732,6 @@ public enum DiagnosticErrorCode implements DiagnosticCode { CONTINUE_NOT_ALLOWED("BCE3992", "continue.not.allowed"), BREAK_NOT_ALLOWED("BCE3993", "break.not.allowed"), - XML_FUNCTION_DOES_NOT_SUPPORT_ARGUMENT_TYPE("BCE3995", "xml.function.does.not.support.argument.type"), INTERSECTION_NOT_ALLOWED_WITH_TYPE("BCE3996", "intersection.not.allowed.with.type"), ASYNC_SEND_NOT_YET_SUPPORTED_AS_EXPRESSION("BCE3997", "async.send.action.not.yet.supported.as.expression"), diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/Desugar.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/Desugar.java index 09e64815b208..683de4d78266 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/Desugar.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/Desugar.java @@ -6742,7 +6742,7 @@ private void visitArgs(BLangInvocation invocation) { reorderArguments(invocation); rewriteExprs(invocation.requiredArgs); - fixStreamTypeCastsInInvocationParams(invocation); + fixStreamAndXmlTypeCastsInInvocationParams(invocation); fixNonRestArgTypeCastInTypeParamInvocation(invocation); rewriteExprs(invocation.restArgs); @@ -7188,13 +7188,14 @@ private BLangExpression fixTypeCastInTypeParamInvocation(BLangInvocation iExpr, return types.addConversionExprIfRequired(genIExpr, originalInvType); } - private void fixStreamTypeCastsInInvocationParams(BLangInvocation iExpr) { + private void fixStreamAndXmlTypeCastsInInvocationParams(BLangInvocation iExpr) { List requiredArgs = iExpr.requiredArgs; List params = ((BInvokableSymbol) iExpr.symbol).params; if (!params.isEmpty()) { for (int i = 0; i < requiredArgs.size(); i++) { BVarSymbol param = params.get(i); - if (Types.getImpliedType(param.type).tag == TypeTags.STREAM) { + int tag = Types.getImpliedType(param.type).tag; + if (tag == TypeTags.STREAM || tag == TypeTags.XML) { requiredArgs.set(i, types.addConversionExprIfRequired(requiredArgs.get(i), param.type)); } } 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 0200b52ed18e..650f86031181 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 @@ -8910,10 +8910,10 @@ private BType checkIndexAccessExpr(BLangIndexBasedAccess indexBasedAccessExpr, A if (type == symTable.semanticError) { return type; } - // Note: out of range member access returns empty xml value unlike lists - // hence, this needs to be set to xml type - indexBasedAccessExpr.originalType = varRefType; - actualType = varRefType; + + BType xmlMemberAccessType = getXmlMemberAccessType(varRefType); + indexBasedAccessExpr.originalType = xmlMemberAccessType; + actualType = xmlMemberAccessType; } else if (varRefType.tag == TypeTags.TABLE) { if (indexBasedAccessExpr.isLValue) { dlog.error(indexBasedAccessExpr.pos, DiagnosticErrorCode.CANNOT_UPDATE_TABLE_USING_MEMBER_ACCESS, @@ -8965,6 +8965,34 @@ private BType checkIndexAccessExpr(BLangIndexBasedAccess indexBasedAccessExpr, A return actualType; } + private BType getXmlMemberAccessType(BType varRefType) { + BType xmlMemberAccessType; + + if (varRefType.tag == TypeTags.UNION) { + LinkedHashSet memberTypes = ((BUnionType) varRefType).getMemberTypes(); + LinkedHashSet effectiveMemberTypes = new LinkedHashSet<>(memberTypes.size()); + for (BType memberType : memberTypes) { + memberType = Types.getImpliedType(memberType); + if (memberType == symTable.xmlNeverType) { + effectiveMemberTypes.add(symTable.xmlNeverType); + continue; + } + effectiveMemberTypes.add(getXMLConstituents(memberType)); + } + xmlMemberAccessType = effectiveMemberTypes.size() == 1 ? effectiveMemberTypes.iterator().next() : + BUnionType.create(null, effectiveMemberTypes); + } else { + xmlMemberAccessType = getXMLConstituents(varRefType); + } + + if (types.isAssignable(xmlMemberAccessType, symTable.neverType)) { + return symTable.xmlNeverType; + } else if (types.isAssignable(symTable.xmlNeverType, xmlMemberAccessType)) { + return xmlMemberAccessType; + } + return BUnionType.create(null, xmlMemberAccessType, symTable.xmlNeverType); + } + private Long getConstIndex(BLangExpression indexExpr) { return switch (indexExpr.getKind()) { case GROUP_EXPR -> { diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeParamAnalyzer.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeParamAnalyzer.java index 3c7f9f8a9dee..731978cb27fc 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeParamAnalyzer.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeParamAnalyzer.java @@ -388,10 +388,7 @@ private void visitType(BLangExpression expr, Location loc, BType expType, BType // Bound type is a structure. Visit recursively to find bound type. switch (expType.tag) { case TypeTags.XML: - if (!TypeTags.isXMLTypeTag(Types.getImpliedType(actualType).tag)) { - if (Types.getImpliedType(actualType).tag == TypeTags.UNION) { - dlog.error(loc, DiagnosticErrorCode.XML_FUNCTION_DOES_NOT_SUPPORT_ARGUMENT_TYPE, actualType); - } + if (!types.isAssignable(actualType, symTable.xmlType)) { return; } switch (actualType.tag) { @@ -653,6 +650,7 @@ private void findTypeParamInUnion(Location loc, BType expType, BUnionType actual if (TypeTags.isXMLTypeTag(referredType.tag)) { if (referredType.tag == TypeTags.XML) { members.add(((BXMLType) referredType).constraint); + continue; } members.add(type); } diff --git a/compiler/ballerina-lang/src/main/resources/compiler.properties b/compiler/ballerina-lang/src/main/resources/compiler.properties index 7d3262cd4bfa..58b4fa297116 100644 --- a/compiler/ballerina-lang/src/main/resources/compiler.properties +++ b/compiler/ballerina-lang/src/main/resources/compiler.properties @@ -721,10 +721,6 @@ error.invalid.namespace.declaration=\ error.cannot.update.xml.sequence=\ cannot update an xml sequence -error.xml.function.does.not.support.argument.type=\ - xml langlib functions does not support union types as their arguments - - error.invalid.xml.ns.interpolation=\ xml namespaces cannot be interpolated diff --git a/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibXMLTest.java b/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibXMLTest.java index d9385e374ba3..9fcfb5ef1466 100644 --- a/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibXMLTest.java +++ b/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibXMLTest.java @@ -367,6 +367,11 @@ public void testXmlFilterValueAndErrorWithNonElementSingletonValues() { BRunUtil.invoke(compileResult, "testXmlFilterValueAndErrorWithNonElementSingletonValues"); } + @Test + public void testLangLibCallsWithUnions() { + BRunUtil.invoke(compileResult, "testLangLibCallsWithUnions"); + } + @Test public void testNegativeCases() { negativeResult = BCompileUtil.compile("test-src/xmllib_test_negative.bal"); @@ -392,6 +397,28 @@ public void testNegativeCases() { 97, 62); validateError(negativeResult, i++, "incompatible types: expected 'string', found 'xml:Element'", 98, 41); + validateError(negativeResult, i++, "incompatible types: expected 'xml', " + + "found '(xml|xml|int)'", 103, 13); + validateError(negativeResult, i++, "incompatible types: expected 'xml', " + + "found '(xml|xml|int)'", 104, 20); + validateError(negativeResult, i++, "incompatible types: expected 'object { " + + "public isolated function next () returns (" + + "record {| xml:Comment value; |}?); }', found 'object { " + + "public isolated function next () returns (" + + "record {| (xml:Comment|xml:ProcessingInstruction) value; |}?); }'", 111, 11); + validateError(negativeResult, i++, "incompatible types: expected 'xml:Comment', " + + "found '(xml:Comment|xml:ProcessingInstruction)'", 113, 21); + validateError(negativeResult, i++, "incompatible types: expected 'xml:ProcessingInstruction', " + + "found '(xml:Comment|xml:ProcessingInstruction)'", 114, 35); + validateError(negativeResult, i++, "incompatible types: expected 'xml:Comment', " + + "found '(xml:Comment|xml:Element)'", 117, 21); + validateError(negativeResult, i++, "incompatible types: expected 'xml:Element', " + + "found '(xml:Comment|xml:Element)'", 118, 21); + validateError(negativeResult, i++, "incompatible types: expected 'xml', " + + "found '(xml:Element|never)'", 121, 20); + validateError(negativeResult, i++, "incompatible types: expected 'xml:Element', found 'xml'", 124, 21); + validateError(negativeResult, i++, "incompatible types: expected 'xml:ProcessingInstruction', " + + "found 'xml:Comment'", 127, 35); assertEquals(negativeResult.getErrorCount(), i); } @@ -449,8 +476,8 @@ public void testNegativeConstraint() { 25, 28); validateError(constraintNegative, i++, "incompatible types: expected 'xml'," + " found 'xml:Element'", 26, 42); - validateError(constraintNegative, i++, "incompatible types: expected 'xml:Comment', found 'xml'", - 29, 26); + validateError(constraintNegative, i++, "incompatible types: expected 'xml:Comment', " + + "found '(xml:Element|xml)'", 29, 26); validateError(constraintNegative, i++, "incompatible types: expected 'xml', found 'xml:Comment'", 32, 41); validateError(constraintNegative, i++, "incompatible types: expected 'xml'," + diff --git a/langlib/langlib-test/src/test/resources/test-src/xmllib_test.bal b/langlib/langlib-test/src/test/resources/test-src/xmllib_test.bal index b224f79a9919..3292e44780f0 100644 --- a/langlib/langlib-test/src/test/resources/test-src/xmllib_test.bal +++ b/langlib/langlib-test/src/test/resources/test-src/xmllib_test.bal @@ -1251,6 +1251,56 @@ function testXmlFilterValueAndErrorWithNonElementSingletonValues() { "incompatible types: 'lang.xml:Text' cannot be cast to 'lang.xml:Element'"); } +function testLangLibCallsWithUnions() { + xml|xml s1 = > xml `val`; + int length = s1.length(); + test:assertValueEqual(length, 2); + length = xml:length(s1); + test:assertValueEqual(length, 2); + + xml|xml s2 = > xml ``; + object { + public isolated function next() returns record {| + xml:Comment|xml:ProcessingInstruction value; + |}?; + } iterator = s2.iterator(); + test:assertValueEqual(iterator.next(), {value: xml ``}); + test:assertValueEqual(iterator.next(), {value: xml ``}); + test:assertValueEqual(iterator.next(), ()); + + xml:Comment|xml:Element get = s1.get(1); + test:assertValueEqual(get, xml `val`); + test:assertValueEqual(xml:get(s1, 1), xml `val`); + + xml:Comment|xml:Element get2 = get.get(0); + test:assertValueEqual(get2, xml `val`); + + get2 = xml:get(get, 0); + test:assertValueEqual(get2, xml `val`); + + xml:Element|xml v1 = xml ``; + xml:Element get3 = v1.get(0); + test:assertValueEqual(get3, xml ``); + + var fn = function () { + xml:Element|xml v = xml ``; + xml:Element _ = v.get(1); + panic error("expected the get call to panic"); + }; + error? fnRes = trap fn(); + test:assertTrue(fnRes is error); + error err = fnRes; + test:assertValueEqual(err.message(), "xml sequence index out of range. Length: '1' requested: '1'"); + + xml:Element|xml:Element v2 = xml `HamletMacbeth`; + xml children = v2.getChildren(); + test:assertValueEqual(children, xml `HamletMacbeth`); + + xml|xml s3 = > xml ``; + xml:Comment get4 = s3.get(0); + test:assertValueEqual(get4, xml ``); +} + type Error error; function assertError(any|error value, string errorMessage, string expDetailMessage) { diff --git a/langlib/langlib-test/src/test/resources/test-src/xmllib_test_negative.bal b/langlib/langlib-test/src/test/resources/test-src/xmllib_test_negative.bal index e9aa60db1cdb..620a86c45d8f 100644 --- a/langlib/langlib-test/src/test/resources/test-src/xmllib_test_negative.bal +++ b/langlib/langlib-test/src/test/resources/test-src/xmllib_test_negative.bal @@ -97,3 +97,32 @@ function testCreateElement() { xml:Element r3 = 'xml:createElement("elem", attributes3, "hello"); xml:Element r4 = 'xml:createElement(xml ``, attributes3, children1); } + +function testLangLibCallsWithUnionsNegative() { + xml|xml|int s1 = > xml `val`; + int _ = s1.length(); + _ = xml:length(s1); + + xml|xml s2 = > xml ``; + object { + public isolated function next() returns record {| + xml:Comment value; + |}?; + } _ = s2.iterator(); + + xml:Comment _ = s2.get(1); + xml:ProcessingInstruction _ = xml:get(s2, 1); + + xml:Comment|xml:Element v = xml ``; + xml:Comment _ = v.get(0); + xml:Element _ = xml:get(v, 0); + + xml:Element|xml v1 = xml ``; + xml _ = v1.get(0); + + xml:Element|xml:Element v2 = xml `HamletMacbeth`; + xml:Element _ = v2.getChildren(); + + xml|xml s3 = > xml ``; + xml:ProcessingInstruction _ = s3.get(0); +} diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/access/XmlMemberAccessTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/access/XmlMemberAccessTest.java new file mode 100644 index 000000000000..59e1efeb63b0 --- /dev/null +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/access/XmlMemberAccessTest.java @@ -0,0 +1,112 @@ +/* + * 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.expressions.access; + +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.DataProvider; +import org.testng.annotations.Test; + +import static org.ballerinalang.test.BAssertUtil.validateError; + +/** + * Test cases for XML member access. + * + * @since 2201.11.0 + */ +public class XmlMemberAccessTest { + + private CompileResult result; + + @BeforeClass + public void setup() { + result = BCompileUtil.compile("test-src/expressions/access/xml_member_access.bal"); + } + + @Test + public void testNegativeCases() { + CompileResult negativeResult = BCompileUtil.compile( + "test-src/expressions/access/xml_member_access_negative.bal"); + int i = 0; + validateError(negativeResult, i++, "incompatible types: expected 'xml:Element', " + + "found '(xml:Element|xml:Comment|xml:ProcessingInstruction|xml:Text)'", 20, 21); + validateError(negativeResult, i++, "incompatible types: expected " + + "'(xml:Element|xml:Comment|xml:ProcessingInstruction)', found " + + "'(xml:Element|xml:Comment|xml:ProcessingInstruction|xml:Text)'", 21, 59); + validateError(negativeResult, i++, "incompatible types: expected " + + "'(xml:Comment|xml:ProcessingInstruction|xml:Text)', found " + + "'(xml:Element|xml:Comment|xml:ProcessingInstruction|xml:Text)'", 22, 56); + validateError(negativeResult, i++, "incompatible types: expected 'xml:Element', " + + "found '(xml:Element|xml)'", 27, 21); + validateError(negativeResult, i++, "incompatible types: expected 'xml', " + + "found '(xml:Element|xml)'", 28, 20); + validateError(negativeResult, i++, "incompatible types: expected 'xml:Comment', " + + "found '(xml:Comment|xml)'", 33, 21); + validateError(negativeResult, i++, "incompatible types: expected 'xml', " + + "found '(xml:Comment|xml)'", 34, 20); + validateError(negativeResult, i++, "incompatible types: expected 'xml', found 'xml:Text'", 39, 20); + validateError(negativeResult, i++, "incompatible types: expected 'xml:ProcessingInstruction', " + + "found '(xml:ProcessingInstruction|xml)'", 44, 35); + validateError(negativeResult, i++, "incompatible types: expected 'xml', " + + "found '(xml:ProcessingInstruction|xml)'", 45, 20); + validateError(negativeResult, i++, "incompatible types: expected 'xml:Element', " + + "found '(xml:Element|xml)'", 50, 21); + validateError(negativeResult, i++, "incompatible types: expected 'xml', found 'xml:Text'", 53, 20); + validateError(negativeResult, i++, "incompatible types: expected 'xml:Comment', " + + "found '(xml:Comment|xml)'", 56, 21); + validateError(negativeResult, i++, "incompatible types: expected 'xml:ProcessingInstruction', " + + "found '(xml:ProcessingInstruction|xml)'", 59, 35); + validateError(negativeResult, i++, "incompatible types: expected '(xml:Element|xml:Comment)', " + + "found '(xml:Element|xml:Comment|xml)'", 64, 33); + validateError(negativeResult, i++, "incompatible types: expected '(xml:Element|xml)', " + + "found '(xml:Element|xml:Comment|xml)'", 65, 32); + validateError(negativeResult, i++, "incompatible types: expected '(xml:Comment|xml)', " + + "found '(xml:Comment|xml:ProcessingInstruction|never|xml)'", 68, 32); + validateError(negativeResult, i++, "incompatible types: expected '(xml:Element|xml:Comment)', " + + "found '(xml:Element|xml:Comment|xml)'", 73, 33); + validateError(negativeResult, i++, "incompatible types: expected '(xml:Comment|xml)', " + + "found '(xml:Element|xml:Comment|xml)'", 74, 32); + validateError(negativeResult, i++, "incompatible types: expected '(xml:Comment|xml)', " + + "found '(xml:Comment|xml:ProcessingInstruction|never|xml)'", 77, 32); + validateError(negativeResult, i++, "incompatible types: expected 'xml:Element', found 'xml'", 82, 21); + Assert.assertEquals(negativeResult.getErrorCount(), i); + } + + @Test(dataProvider = "xmlMemberAccessFunctions") + public void testXmlMemberAccess(String function) { + BRunUtil.invoke(result, function); + } + + @DataProvider(name = "xmlMemberAccessFunctions") + public Object[][] xmlMemberAccessFunctions() { + return new Object[][] { + { "testXmlMemberAccessOnXml" }, + { "testXmlMemberAccessOnXmlElementSequences" }, + { "testXmlMemberAccessOnXmlCommentSequences" }, + { "testXmlMemberAccessOnXmlTextSequences" }, + { "testXmlMemberAccessOnXmlProcessingInstructionSequences" }, + { "testXmlMemberAccessOnXmlSingletons" }, + { "testXmlMemberAccessOnXmlUnions" }, + { "testXmlMemberAccessOnXmlUnionSequences" }, + { "testXmlMemberAccessOnEmptySequenceType" } + }; + } +} diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/types/xml/XMLIterationTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/types/xml/XMLIterationTest.java index 8f7d1f61afab..10312a5cad1c 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/types/xml/XMLIterationTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/types/xml/XMLIterationTest.java @@ -104,19 +104,13 @@ public void testNegative() { "incompatible types: '(xml|xml)' is not an iterable collection", 68, 44); BAssertUtil.validateError(negative, index++, - "incompatible types: expected 'record {| (xml:Element|xml:Text) value; |}?', " + - "found 'record {| ballerina/lang.xml:0.0.0:ItemType value; |}?'", - 72, 68); - BAssertUtil.validateError(negative, index++, - "xml langlib functions does not support union types as their arguments", - 72, 68); - BAssertUtil.validateError(negative, index++, - "incompatible types: expected 'record {| (xml:Element|xml:Text) value; |}?', " + - "found 'record {| ballerina/lang.xml:0.0.0:ItemType value; |}?'", - 73, 68); + "incompatible types: expected 'record {| xml:Element value; |}?', found " + + "'record {| (xml:Element|xml:Text) value; |}?'", + 72, 58); BAssertUtil.validateError(negative, index++, - "xml langlib functions does not support union types as their arguments", - 73, 68); + "incompatible types: expected 'record {| xml:Text value; |}?', found " + + "'record {| (xml:Element|xml:Text) value; |}?'", + 73, 55); BAssertUtil.validateError(negative, index++, "incompatible types: 'xml' cannot be constrained with 'xml:Element[]'", 77, 5); diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/access/xml_member_access.bal b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/access/xml_member_access.bal new file mode 100644 index 000000000000..5fc23e6e4778 --- /dev/null +++ b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/access/xml_member_access.bal @@ -0,0 +1,214 @@ +// 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. + +function testXmlMemberAccessOnXml() { + xml seq = xml `text itemelement item`; + + xml m1 = seq[0]; + assertTrue(m1 is xml:Text); + assertEquality(xml `text item`, m1); + + xml:Element|xml:Comment|xml:ProcessingInstruction|xml:Text m2 = seq[1]; + assertTrue(m2 is xml:Element); + assertEquality(xml `element item`, m2); + + xml m3 = seq[2]; + assertTrue(m3 is xml:Comment); + assertEquality(xml ``, m3); + + xml m4 = seq[3]; + assertTrue(m4 is xml:ProcessingInstruction); + assertEquality(xml ``, m4); + + xml m5 = seq[4]; + assertTrue(m5 is xml); +} + +function testXmlMemberAccessOnXmlElementSequences() { + xml seq = xml `element 1element 2`; + + xml:Element|xml m1 = seq[0]; + assertTrue(m1 is xml:Element); + assertEquality(xml `element 1`, m1); + + xml:Element|xml m2 = seq[1]; + assertTrue(m2 is xml:Element); + assertEquality(xml `element 2`, m2); + + xml:Element|xml m3 = seq[5]; + assertTrue(m3 is xml); + + var fn = function () { + xml:Element|xml _ = seq[-5]; + panic error("expected member access to panic"); + }; + + error? fnRes = trap fn(); + assertTrue(fnRes is error); + error err = fnRes; + assertEquality("xml sequence index out of range. Length: '2' requested: '-5'", err.message()); +} + +function testXmlMemberAccessOnXmlCommentSequences() { + xml seq = xml ``; + + xml:Comment|xml m1 = seq[0]; + assertTrue(m1 is xml:Comment); + assertEquality(xml ``, m1); + + xml:Comment|xml m2 = seq[2]; + assertTrue(m2 is xml:Comment); + assertEquality(xml ``, m2); + + xml:Comment|xml m3 = seq[5]; + assertTrue(m3 is xml); + + var fn = function () { + xml:Comment|xml _ = seq[-1]; + panic error("expected member access to panic"); + }; + + error? fnRes = trap fn(); + assertTrue(fnRes is error); + error err = fnRes; + assertEquality("xml sequence index out of range. Length: '3' requested: '-1'", err.message()); +} + +function testXmlMemberAccessOnXmlTextSequences() { + xml seq = xml `baz`; + + xml:Text|xml m1 = seq[0]; + assertTrue(m1 !is xml); + assertEquality(xml `baz`, m1); + + xml:Text|xml m3 = seq[2]; + assertTrue(m3 is xml); + + var fn = function () { + xml:Text|xml _ = seq[-1]; + panic error("expected member access to panic"); + }; + + error? fnRes = trap fn(); + assertTrue(fnRes is error); + error err = fnRes; + assertEquality("xml sequence index out of range. Length: '1' requested: '-1'", err.message()); +} + +function testXmlMemberAccessOnXmlProcessingInstructionSequences() { + xml seq = xml ``; + + xml:ProcessingInstruction|xml m1 = seq[0]; + assertTrue(m1 is xml:ProcessingInstruction); + assertEquality(xml ``, m1); + + xml:ProcessingInstruction|xml m3 = seq[2]; + assertTrue(m3 is xml); + + var fn = function () { + xml:ProcessingInstruction|xml _ = seq[-1]; + panic error("expected member access to panic"); + }; + + error? fnRes = trap fn(); + assertTrue(fnRes is error); + error err = fnRes; + assertEquality("xml sequence index out of range. Length: '2' requested: '-1'", err.message()); +} + +function testXmlMemberAccessOnXmlSingletons() { + xml:Element elem = xml `element value`; + xml:Element|xml m1 = elem[0]; + assertTrue(m1 is xml:Element); + assertEquality(elem, m1); + assertTrue(elem[3] is xml); + + xml:Text text = xml `text value`; + xml:Text m2 = text[0]; + assertTrue(m2 !is xml); + assertEquality(text, m2); + assertTrue(text[3] is xml); + + xml:Comment comment = xml ``; + xml:Comment|xml m3 = comment[0]; + assertTrue(m3 is xml:Comment); + assertEquality(comment, m3); + assertTrue(comment[2] is xml); + + xml:ProcessingInstruction pi = xml ``; + xml:ProcessingInstruction|xml m4 = pi[0]; + assertTrue(m4 is xml:ProcessingInstruction); + assertEquality(pi, m4); + assertTrue(pi[4] is xml); +} + +function testXmlMemberAccessOnXmlUnions() { + xml|xml seq = > xml `val`; + xml:Element|xml:Comment|xml m1 = seq[0]; + assertTrue(m1 is xml:Element); + assertEquality(xml ``, m1); + + xml:Element|xml:Comment|xml m2 = seq[1]; + assertTrue(m2 is xml:Element); + assertEquality(xml `val`, m2); + + assertTrue(seq[4] is xml); + + xml|xml:ProcessingInstruction|xml seq2 = xml ``; + xml:Comment|xml:ProcessingInstruction|xml m3 = seq2[0]; + assertTrue(m3 is xml:ProcessingInstruction); + assertEquality(xml ``, m3); + + assertTrue(seq2[1] is xml); +} + +function testXmlMemberAccessOnXmlUnionSequences() { + xml seq = xml `val`; + xml:Element|xml:Comment|xml m1 = seq[0]; + assertTrue(m1 is xml:Element); + assertEquality(xml ``, m1); + + xml:Element|xml:Comment|xml m2 = seq[1]; + assertTrue(m2 is xml:Comment); + assertEquality(xml ``, m2); + + assertEquality(xml `val`, seq[2]); + + assertTrue(seq[4] is xml); + + xml seq2 = xml ``; + xml:Comment|xml:ProcessingInstruction|xml m3 = seq2[0]; + assertTrue(m3 is xml:ProcessingInstruction); + assertEquality(xml ``, m3); + + assertTrue(seq2[1] is xml); +} + +function testXmlMemberAccessOnEmptySequenceType() { + xml seq = xml ``; + xml val = seq[0]; + assertEquality(xml ``, val); +} + +function assertTrue(anydata actual) { + assertEquality(true, actual); +} + +function assertEquality(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/expressions/access/xml_member_access_negative.bal b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/access/xml_member_access_negative.bal new file mode 100644 index 000000000000..bafd570062f9 --- /dev/null +++ b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/access/xml_member_access_negative.bal @@ -0,0 +1,83 @@ +// 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. + +function testXmlMemberAccessOnXmlNegative() { + xml seq = xml `text itemelement item`; + + xml:Element _ = seq[0]; + xml:Element|xml:Comment|xml:ProcessingInstruction _ = seq[1]; + xml:Comment|xml:ProcessingInstruction|xml:Text _ = seq[2]; +} + +function testXmlMemberAccessOnXmlElementSequencesNegative() { + xml seq = xml `element 1element 2`; + xml:Element _ = seq[0]; + xml _ = seq[1]; +} + +function testXmlMemberAccessOnXmlCommentSequencesNegative() { + xml seq = xml ``; + xml:Comment _ = seq[0]; + xml _ = seq[2]; +} + +function testXmlMemberAccessOnXmlTextSequencesNegative() { + xml seq = xml `baz`; + xml _ = seq[0]; +} + +function testXmlMemberAccessOnXmlProcessingInstructionSequencesNegative() { + xml seq = xml ``; + xml:ProcessingInstruction _ = seq[0]; + xml _ = seq[2]; +} + +function testXmlMemberAccessOnXmlSingletonsNegative() { + xml:Element elem = xml `element value`; + xml:Element _ = elem[0]; + + xml:Text text = xml `text value`; + xml _ = text[0]; + + xml:Comment comment = xml ``; + xml:Comment _ = comment[0]; + + xml:ProcessingInstruction pi = xml ``; + xml:ProcessingInstruction _ = pi[0]; +} + +function testXmlMemberAccessOnXmlUnionsNegative() { + xml|xml seq = > xml `val`; + xml:Element|xml:Comment _ = seq[0]; + xml:Element|xml _ = seq[1]; + + xml|xml:ProcessingInstruction|xml seq2 = xml ``; + xml:Comment|xml _ = seq2[0]; +} + +function testXmlMemberAccessOnXmlUnionSequencesNegative() { + xml seq = xml `val`; + xml:Element|xml:Comment _ = seq[0]; + xml:Comment|xml _ = seq[1]; + + xml seq2 = xml ``; + xml:Comment|xml _ = seq2[0]; +} + +function testXmlMemberAccessOnEmptySequenceTypeNegative() { + xml seq = xml ``; + xml:Element _ = seq[0]; +} diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml_iteration_negative.bal b/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml_iteration_negative.bal index 175a415c9031..8f5c484fa3a1 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml_iteration_negative.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml_iteration_negative.bal @@ -69,8 +69,8 @@ function xmlTypeParamUnionIter() { concatString(elem.toString()); } - record {| 'xml:Element|'xml:Text value; |}? nextUnionXMLVal2 = el2.iterator().next(); - record {| 'xml:Element|'xml:Text value; |}? nextUnionXMLVal3 = el3.iterator().next(); + record {| 'xml:Element value; |}? nextUnionXMLVal2 = el2.iterator().next(); + record {| 'xml:Text value; |}? nextUnionXMLVal3 = el3.iterator().next(); } function xmlElementTypeArrayIter() {