diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/SemanticAnalyzer.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/SemanticAnalyzer.java index 3e2a6bb5831c..e421fc71cfe1 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/SemanticAnalyzer.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/SemanticAnalyzer.java @@ -4480,7 +4480,7 @@ private void handleForeachDefinitionVariables(VariableDefinitionNode variableDef // If the type node is available, we get the type from it. BType typeNodeType = symResolver.resolveTypeNode(variableNode.typeNode, blockEnv); // Checking whether the RHS type is assignable to LHS type. - if (types.isAssignable(varType, typeNodeType)) { + if (types.constituentTypesAssignable(varType, typeNodeType)) { if (onFail && varType == symTable.neverType) { varType = typeNodeType; } diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/Types.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/Types.java index b6f80c1f19cf..855fd31e18af 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/Types.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/Types.java @@ -1456,7 +1456,7 @@ private boolean isFunctionTypeAssignable(BInvokableType source, BInvokableType t boolean isTypeParam = TypeParamAnalyzer.isTypeParam(targetParam); if (isTypeParam) { - if (!isAssignable(sourceParam, targetParam)) { + if (!constituentTypesAssignable(sourceParam, targetParam)) { return false; } } else { @@ -1481,6 +1481,17 @@ private boolean isFunctionTypeAssignable(BInvokableType source, BInvokableType t return checkFunctionTypeEquality(source, target, unresolvedTypes, (s, t, ut) -> isAssignable(t, s, ut)); } + public boolean constituentTypesAssignable(BType varType, BType typeNodeType) { + if (varType.tag == TypeTags.XML) { + typeNodeType = getReferredType(typeNodeType); + if (typeNodeType.tag == TypeTags.UNION) { + return isAssignable(symTable.xmlItemType, typeNodeType); + } + return typeNodeType.tag == TypeTags.XML; + } + return isAssignable(varType, typeNodeType); + } + public boolean isInherentlyImmutableType(BType type) { type = getImpliedType(type); if (isValueType(type)) { @@ -3681,11 +3692,6 @@ private boolean isAssignableToUnionType(BType source, BType target, Set>|xml>)', found 'xml'", 150, 54); BAssertUtil.validateError(negativeResult, index++, "missing gt token", 154, 22); BAssertUtil.validateError(negativeResult, index++, "missing gt token", 155, 28); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 166, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 169, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 172, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 175, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 178, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 181, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 184, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 187, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 190, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 193, 12); + BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected 'UX', found 'X'", 196, 12); Assert.assertEquals(index, negativeResult.getErrorCount()); } @@ -456,6 +467,11 @@ public void testXMLLiteralWithConditionExpr() { BRunUtil.invoke(result, "testXMLLiteralWithConditionExpr"); } + @Test + public void testXMLSubtype() { + BRunUtil.invoke(result, "testXMLSubtype"); + } + @AfterClass public void tearDown() { result = null; diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml-literals-negative.bal b/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml-literals-negative.bal index 3952bce53638..cbdd6a92d217 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml-literals-negative.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml-literals-negative.bal @@ -154,3 +154,44 @@ function testMissingClosingGTToken() { xml x1 = xml ``; xml x2 = xml ``; } + +type X xml; +type XE xml; +type XP xml; +type XC xml; +type UX XE|XP|XC|xml:Text; + +function testXMLInvalidSubtype() { + X x1 = xml ``; + UX _ = x1; + + X x2 = xml ``; + UX _ = x2; + + X x3 = xml ``; + UX _ = x3; + + X x4 = xml `random sequence of text`; + UX _ = x4; + + X x5 = xml ``; + UX _ = x5; + + X x6 = xml ``; + UX _ = x6; + + X x7 = xml `random text`; + UX _ = x7; + + X x8 = xml `text`; + UX _ = x8; + + X x9 = xml ``; + UX _ = x9; + + X x10 = xml `text`; + UX _ = x10; + + X x11 = xml `?>text`; + UX _ = x11; +} diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml-literals.bal b/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml-literals.bal index 860cb022a42d..e6b6ffd17f18 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml-literals.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/types/xml/xml-literals.bal @@ -467,3 +467,75 @@ function testXMLLiteralWithConditionExpr() { xml w3 = (s ?: (s ?: (v1 is xml:Element ? e2 : xml `Foo`))) + xml `B`; test:assertEquals(w3.toString(), "FooB"); } + +type X xml; +type XE xml; +type XP xml; +type XC xml; +type UX XE|XP|XC|xml:Text; + +function testXMLSubtype() { + X x1 = xml ``; + UX _ = x1; + + X x2 = xml ``; + UX _ = x2; + + X x3 = xml ``; + UX _ = x3; + + X x4 = xml `random sequence of text`; + UX _ = x4; + + UX|error ux; + X x5 = xml ``; + ux = trap x5; + assertError(ux, "{ballerina}TypeCastError", + "incompatible types: 'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>' cannot be cast to 'UX'"); + + X x6 = xml ``; + ux = trap x6; + assertError(ux, "{ballerina}TypeCastError", + "incompatible types: 'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>' cannot be cast to 'UX'"); + + X x7 = xml `random text`; + ux = trap x7; + assertError(ux, "{ballerina}TypeCastError", + "incompatible types: 'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>' cannot be cast to 'UX'"); + + X x8 = xml `text`; + ux = trap x8; + assertError(ux, "{ballerina}TypeCastError", + "incompatible types: 'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>' cannot be cast to 'UX'"); + + X x9 = xml ``; + ux = trap x9; + assertError(ux, "{ballerina}TypeCastError", + "incompatible types: 'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>' cannot be cast to 'UX'"); + + X x10 = xml `text`; + ux = trap x10; + assertError(ux, "{ballerina}TypeCastError", + "incompatible types: 'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>' cannot be cast to 'UX'"); + + X x11 = xml `?>text`; + ux = trap x11; + assertError(ux, "{ballerina}TypeCastError", + "incompatible types: 'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>' cannot be cast to 'UX'"); +} + +type Error error; + +function assertError(any|error value, string errorMessage, string expDetailMessage) { + if value is Error { + if value.message() != errorMessage { + panic error("Expected error message: " + errorMessage + " found: " + value.message()); + } + + if value.detail().message == expDetailMessage { + return; + } + panic error("Expected error detail message: " + expDetailMessage + " found: " + value.detail().message); + } + panic error("Expected: Error, found: " + (typeof value).toString()); +}