diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/PredefinedTypes.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/PredefinedTypes.java index 2dc13a546404..7f941c9bc783 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/PredefinedTypes.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/PredefinedTypes.java @@ -89,7 +89,7 @@ */ public class PredefinedTypes { - private static final Module EMPTY_MODULE = new Module(null, null, null); + public static final Module EMPTY_MODULE = new Module(null, null, null); public static final IntegerType TYPE_INT = new BIntegerType(TypeConstants.INT_TNAME, EMPTY_MODULE); public static final IntegerType TYPE_INT_SIGNED_8 = diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/constants/RuntimeConstants.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/constants/RuntimeConstants.java index 42cd5f0f4400..57daf73d3e23 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/constants/RuntimeConstants.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/constants/RuntimeConstants.java @@ -90,6 +90,8 @@ public class RuntimeConstants { // Empty value for string public static final BString STRING_EMPTY_VALUE = StringUtils.fromString(""); + public static final Long INT_MAX_VALUE = 9223372036854775807L; + public static final Long INT_MIN_VALUE = -9223372036854775807L - 1L; public static final Integer BBYTE_MIN_VALUE = 0; public static final Integer BBYTE_MAX_VALUE = 255; public static final Integer SIGNED32_MAX_VALUE = 2147483647; diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BasicTypeBitSet.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BasicTypeBitSet.java new file mode 100644 index 000000000000..47e72ce4cd06 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BasicTypeBitSet.java @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package io.ballerina.runtime.api.types.semtype; + +// SEMTYPE-TODO: revisit this after fully implementing semtypes. Added this to match nBallerina where this is just a +// type alias to int. Maybe not needed here due to the way we have modeled type hierarchy (need to check if doing +// instancof checks on this is faster than checking if some is 0) + +/** + * Represents a union of basic types. + * + * @since 2201.10.0 + */ +public interface BasicTypeBitSet { + + default int some() { + return 0; + } + + default SubType[] subTypeData() { + return new SubType[0]; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BasicTypeCode.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BasicTypeCode.java new file mode 100644 index 000000000000..7402bb44ab15 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BasicTypeCode.java @@ -0,0 +1,116 @@ +/* + * 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. + */ + +package io.ballerina.runtime.api.types.semtype; + +/** + * Represent bit field that indicate which basic type a semType belongs to. + * + * @since 2201.10.0 + */ +public final class BasicTypeCode { + + public static final int CODE_NIL = 0x00; + public static final int CODE_BOOLEAN = 0x01; + public static final int CODE_INT = 0x02; + public static final int CODE_FLOAT = 0x03; + public static final int CODE_DECIMAL = 0x04; + public static final int CODE_STRING = 0x05; + public static final int CODE_ERROR = 0x06; + public static final int CODE_TYPEDESC = 0x07; + public static final int CODE_HANDLE = 0x08; + public static final int CODE_FUNCTION = 0x09; + public static final int CODE_FUTURE = 0x0A; + public static final int CODE_STREAM = 0x0B; + public static final int CODE_LIST = 0x0C; + public static final int CODE_MAPPING = 0x0D; + public static final int CODE_TABLE = 0x0E; + public static final int CODE_XML = 0x0F; + public static final int CODE_OBJECT = 0x10; + public static final int CODE_CELL = 0x11; + public static final int CODE_UNDEF = 0x12; + public static final int CODE_B_TYPE = 0x13; + + // Inherently immutable + public static final BasicTypeCode BT_NIL = from(CODE_NIL); + public static final BasicTypeCode BT_BOOLEAN = from(CODE_BOOLEAN); + public static final BasicTypeCode BT_INT = from(CODE_INT); + public static final BasicTypeCode BT_FLOAT = from(CODE_FLOAT); + public static final BasicTypeCode BT_DECIMAL = from(CODE_DECIMAL); + public static final BasicTypeCode BT_STRING = from(CODE_STRING); + public static final BasicTypeCode BT_ERROR = from(CODE_ERROR); + public static final BasicTypeCode BT_TYPEDESC = from(CODE_TYPEDESC); + public static final BasicTypeCode BT_HANDLE = from(CODE_HANDLE); + public static final BasicTypeCode BT_FUNCTION = from(CODE_FUNCTION); + + // Inherently mutable + public static final BasicTypeCode BT_FUTURE = from(CODE_FUTURE); + public static final BasicTypeCode BT_STREAM = from(CODE_STREAM); + + // Selectively immutable + public static final BasicTypeCode BT_LIST = from(CODE_LIST); + public static final BasicTypeCode BT_MAPPING = from(CODE_MAPPING); + public static final BasicTypeCode BT_TABLE = from(CODE_TABLE); + public static final BasicTypeCode BT_XML = from(CODE_XML); + public static final BasicTypeCode BT_OBJECT = from(CODE_OBJECT); + + // Non-val + public static final BasicTypeCode BT_CELL = from(CODE_CELL); + public static final BasicTypeCode BT_UNDEF = from(CODE_UNDEF); + public static final BasicTypeCode BT_B_TYPE = from(CODE_B_TYPE); + + // Helper bit fields (does not represent basic type tag) + static final int VT_COUNT = CODE_OBJECT + 1; + static final int VT_MASK = (1 << VT_COUNT) - 1; + + static final int VT_COUNT_INHERENTLY_IMMUTABLE = 0x0A; + public static final int VT_INHERENTLY_IMMUTABLE = (1 << VT_COUNT_INHERENTLY_IMMUTABLE) - 1; + + private int code; + + private BasicTypeCode(int code) { + this.code = code; + } + + public static BasicTypeCode from(int code) { + if (BasicTypeCodeCache.isCached(code)) { + return BasicTypeCodeCache.cache[code]; + } + return new BasicTypeCode(code); + } + + public int code() { + return code; + } + + private static final class BasicTypeCodeCache { + + private static final BasicTypeCode[] cache; + static { + cache = new BasicTypeCode[CODE_B_TYPE + 2]; + for (int i = CODE_NIL; i < CODE_B_TYPE + 1; i++) { + cache[i] = new BasicTypeCode(i); + } + } + + private static boolean isCached(int code) { + return 0 < code && code < VT_COUNT; + } + + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Builder.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Builder.java new file mode 100644 index 000000000000..d417a3825203 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Builder.java @@ -0,0 +1,245 @@ +/* + * 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. + */ + +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.internal.types.BType; +import io.ballerina.runtime.internal.types.semtype.BBooleanSubType; +import io.ballerina.runtime.internal.types.semtype.BDecimalSubType; +import io.ballerina.runtime.internal.types.semtype.BFloatSubType; +import io.ballerina.runtime.internal.types.semtype.BIntSubType; +import io.ballerina.runtime.internal.types.semtype.BStringSubType; +import io.ballerina.runtime.internal.values.DecimalValue; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_B_TYPE; + +/** + * Utility class for creating semtypes. + * + * @since 2201.10.0 + */ +public final class Builder { + + private static final String[] EMPTY_STRING_ARR = new String[0]; + + private Builder() { + } + + public static SemType from(BasicTypeCode typeCode) { + if (BasicTypeCache.isCached(typeCode)) { + return BasicTypeCache.cache[typeCode.code()]; + } + return SemType.from(1 << typeCode.code()); + } + + public static SemType from(Type type) { + if (type instanceof SemType semType) { + return semType; + } else if (type instanceof BType bType) { + return from(bType); + } + throw new IllegalArgumentException("Unsupported type: " + type); + } + + public static SemType from(BType innerType) { + return innerType.get(); + } + + public static SemType neverType() { + return basicTypeUnion(0); + } + + public static SemType nilType() { + return from(BasicTypeCode.BT_NIL); + } + + public static SemType intType() { + return from(BasicTypeCode.BT_INT); + } + + public static SemType bType() { + return from(BasicTypeCode.BT_B_TYPE); + } + + public static SemType decimalType() { + return from(BasicTypeCode.BT_DECIMAL); + } + + public static SemType floatType() { + return from(BasicTypeCode.BT_FLOAT); + } + + public static SemType booleanType() { + return from(BasicTypeCode.BT_BOOLEAN); + } + + public static SemType stringType() { + return from(BasicTypeCode.BT_STRING); + } + + public static SemType charType() { + return StringTypeCache.charType; + } + + private static final SemType NEVER = SemType.from(0); + + public static SemType basicTypeUnion(int bitset) { + // TODO: may be cache single type bit sets as well + if (bitset == 0) { + return NEVER; + } else if (Integer.bitCount(bitset) == 1) { + int code = Integer.numberOfTrailingZeros(bitset); + if (BasicTypeCache.isCached(code)) { + return BasicTypeCache.cache[code]; + } + } + return SemType.from(bitset); + } + + public static SemType basicSubType(BasicTypeCode basicTypeCode, SubType subType) { + SubType[] subTypes = initializeSubtypeArray(); + subTypes[basicTypeCode.code()] = subType; + return SemType.from(0, 1 << basicTypeCode.code(), subTypes); + } + + public static SemType intConst(long value) { + if (value >= IntTypeCache.CACHE_MIN_VALUE && value <= IntTypeCache.CACHE_MAX_VALUE) { + return IntTypeCache.cache[(int) value - IntTypeCache.CACHE_MIN_VALUE]; + } + return createIntSingletonType(value); + } + + private static SemType createIntSingletonType(long value) { + List values = new ArrayList<>(1); + values.add(value); + return basicSubType(BasicTypeCode.BT_INT, BIntSubType.createIntSubType(values)); + } + + public static SemType booleanConst(boolean value) { + return value ? BooleanTypeCache.TRUE : BooleanTypeCache.FALSE; + } + + public static SemType intRange(long min, long max) { + return basicSubType(BasicTypeCode.BT_INT, BIntSubType.createIntSubType(min, max)); + } + + public static SemType decimalConst(BigDecimal value) { + BigDecimal[] values = {value}; + return basicSubType(BasicTypeCode.BT_DECIMAL, BDecimalSubType.createDecimalSubType(true, values)); + } + + public static SemType floatConst(double value) { + Double[] values = {value}; + return basicSubType(BasicTypeCode.BT_FLOAT, BFloatSubType.createFloatSubType(true, values)); + } + + public static SemType stringConst(String value) { + BStringSubType subType; + String[] values = {value}; + String[] empty = EMPTY_STRING_ARR; + if (value.length() == 1 || value.codePointCount(0, value.length()) == 1) { + subType = BStringSubType.createStringSubType(true, values, true, empty); + } else { + subType = BStringSubType.createStringSubType(true, empty, true, values); + } + return basicSubType(BasicTypeCode.BT_STRING, subType); + } + + static SubType[] initializeSubtypeArray() { + return new SubType[CODE_B_TYPE + 2]; + } + + public static Optional typeOf(Object object) { + if (object == null) { + return Optional.of(nilType()); + } else if (object instanceof DecimalValue decimalValue) { + return Optional.of(decimalConst(decimalValue.value())); + } else if (object instanceof Double doubleValue) { + return Optional.of(floatConst(doubleValue)); + } else if (object instanceof Number intValue) { + long value = + intValue instanceof Byte byteValue ? Byte.toUnsignedLong(byteValue) : intValue.longValue(); + return Optional.of(intConst(value)); + } else if (object instanceof Boolean booleanValue) { + return Optional.of(booleanConst(booleanValue)); + } else if (object instanceof BString stringValue) { + return Optional.of(stringConst(stringValue.getValue())); + } + return Optional.empty(); + } + + private static final class IntTypeCache { + + private static final int CACHE_MAX_VALUE = 127; + private static final int CACHE_MIN_VALUE = -128; + private static final SemType[] cache; + static { + cache = new SemType[CACHE_MAX_VALUE - CACHE_MIN_VALUE + 1]; + for (int i = CACHE_MIN_VALUE; i <= CACHE_MAX_VALUE; i++) { + cache[i - CACHE_MIN_VALUE] = createIntSingletonType(i); + } + } + } + + private static final class BooleanTypeCache { + + private static final SemType TRUE = createBooleanSingletonType(true); + private static final SemType FALSE = createBooleanSingletonType(false); + + private static SemType createBooleanSingletonType(boolean value) { + return basicSubType(BasicTypeCode.BT_BOOLEAN, BBooleanSubType.from(value)); + } + } + + private static final class StringTypeCache { + + private static final SemType charType; + static { + BStringSubType subTypeData = BStringSubType.createStringSubType(false, Builder.EMPTY_STRING_ARR, true, + Builder.EMPTY_STRING_ARR); + charType = basicSubType(BasicTypeCode.BT_STRING, subTypeData); + } + } + + private static final class BasicTypeCache { + + private static final SemType[] cache; + static { + cache = new SemType[CODE_B_TYPE + 2]; + for (int i = 0; i < CODE_B_TYPE + 1; i++) { + cache[i] = SemType.from(1 << i); + } + } + + private static boolean isCached(BasicTypeCode code) { + int i = code.code(); + return 0 < i && i <= CODE_B_TYPE; + } + + private static boolean isCached(int code) { + return 0 < code && code <= CODE_B_TYPE; + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Context.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Context.java new file mode 100644 index 000000000000..5b6b77441641 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Context.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +package io.ballerina.runtime.api.types.semtype; + +/** + * Context in which semtype was defined in. + * + * @since 2201.10.0 + */ +public class Context { + // SEMTYPE-TODO: Fill this in as needed, currently just a placeholder since basic types don't need it +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Core.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Core.java new file mode 100644 index 000000000000..76c42278ef3f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Core.java @@ -0,0 +1,273 @@ +/* + * 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. + */ + +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.internal.types.semtype.AllOrNothing; +import io.ballerina.runtime.internal.types.semtype.SubTypeData; +import io.ballerina.runtime.internal.types.semtype.SubtypePair; +import io.ballerina.runtime.internal.types.semtype.SubtypePairs; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_B_TYPE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_UNDEF; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.VT_MASK; + +/** + * Contain functions defined in `core.bal` file. + * + * @since 2201.10.0 + */ +public final class Core { + + public static final SemType SEMTYPE_TOP = SemType.from((1 << (CODE_UNDEF + 1)) - 1); + public static final SemType B_TYPE_TOP = SemType.from(1 << BT_B_TYPE.code()); + + private Core() { + } + + public static SemType diff(SemType t1, SemType t2) { + int all1 = t1.all; + int all2 = t2.all; + int some1 = t1.some; + int some2 = t2.some; + if (some1 == 0) { + if (some2 == 0) { + return Builder.basicTypeUnion(all1 & ~all2); + } else { + if (all1 == 0) { + return t1; + } + } + } else { + if (some2 == 0) { + if (all2 == VT_MASK) { + return Builder.basicTypeUnion(0); + } + } + } + int all = all1 & ~(all2 | some2); + int some = (all1 | some1) & ~all2; + some = some & ~all; + if (some == 0) { + return SemType.from(all); + } + SubType[] subtypes = Builder.initializeSubtypeArray(); + for (SubtypePair pair : new SubtypePairs(t1, t2, some)) { + SubType data1 = pair.subType1(); + SubType data2 = pair.subType2(); + int code = pair.typeCode(); + SubType data; + if (data1 == null) { + data = data2.complement(); + } else if (data2 == null) { + data = data1; + } else { + data = data1.diff(data2); + } + if (data.isAll()) { + all |= 1 << code; + some &= ~(1 << code); + } else if (data.isNothing()) { + some &= ~(1 << code); + } else { + subtypes[code] = data; + } + } + return SemType.from(all, some, subtypes); + } + + public static SubType getComplexSubtypeData(SemType t, BasicTypeCode code) { + throw new IllegalStateException("Unimplemented"); + } + + public static SemType union(SemType t1, SemType t2) { + int all1 = t1.all(); + int some1 = t1.some(); + int all2 = t2.all(); + int some2 = t2.some(); + if (some1 == 0) { + if (some2 == 0) { + return Builder.basicTypeUnion(all1 | all2); + } + } + + int all = all1 | all2; + int some = (some1 | some2) & ~all; + if (some == 0) { + return Builder.basicTypeUnion(all); + } + SubType[] subtypes = Builder.initializeSubtypeArray(); + for (SubtypePair pair : new SubtypePairs(t1, t2, some)) { + int code = pair.typeCode(); + SubType data1 = pair.subType1(); + SubType data2 = pair.subType2(); + SubType data; + if (data1 == null) { + data = data2; + } else if (data2 == null) { + data = data1; + } else { + data = data1.union(data2); + } + if (data.isAll()) { + all |= 1 << code; + some &= ~(1 << code); + } else { + subtypes[code] = data; + } + } + if (some == 0) { + return SemType.from(all); + } + return SemType.from(all, some, subtypes); + } + + public static SemType intersect(SemType t1, SemType t2) { + int all1 = t1.all; + int some1 = t1.some; + int all2 = t2.all; + int some2 = t2.some; + if (some1 == 0) { + if (some2 == 0) { + return SemType.from(all1 & all2); + } else { + if (all1 == 0) { + return t1; + } + if (all1 == VT_MASK) { + return t2; + } + } + } else if (some2 == 0) { + if (all2 == 0) { + return t2; + } + if (all2 == VT_MASK) { + return t1; + } + } + + int all = all1 & all2; + int some = (some1 | all1) & (some2 | all2); + some = some & ~all; + if (some == 0) { + return SemType.from(all); + } + + SubType[] subtypes = Builder.initializeSubtypeArray(); + for (SubtypePair pair : new SubtypePairs(t1, t2, some)) { + int code = pair.typeCode(); + SubType data1 = pair.subType1(); + SubType data2 = pair.subType2(); + + SubType data; + if (data1 == null) { + data = data2; + } else if (data2 == null) { + data = data1; + } else { + data = data1.intersect(data2); + } + + if (!data.isNothing()) { + subtypes[code] = data; + } else { + some &= ~(1 << code); + } + } + if (some == 0) { + return SemType.from(all); + } + return SemType.from(all, some, subtypes); + } + + public static boolean isEmpty(Context cx, SemType t) { + if (t.some == 0) { + return t.all == 0; + } + if (t.all != 0) { + return false; + } + for (SubType subType : t.subTypeData()) { + if (subType == null) { + continue; + } + if (!subType.isEmpty()) { + return false; + } + } + return true; + } + + public static SemType complement(SemType t1) { + throw new IllegalStateException("Unimplemented"); + } + + public static boolean isNever(SemType t) { + return t.all == 0 && t.some == 0; + } + + public static boolean isSubType(Context cx, SemType t1, SemType t2) { + // IF t1 and t2 are not pure semtypes calling this is an undefined + return isEmpty(cx, diff(t1, t2)); + } + + public static boolean isSubtypeSimple(SemType t1, SemType t2) { + int bits = t1.all | t1.some; + return (bits & ~t2.all()) == 0; + } + + public static SubTypeData subTypeData(SemType s, BasicTypeCode code) { + if ((s.all & (1 << code.code())) != 0) { + return AllOrNothing.ALL; + } + if (s.some == 0) { + return AllOrNothing.NOTHING; + } + return s.subTypeData()[code.code()].data(); + } + + public static boolean containsBasicType(SemType t1, SemType t2) { + int bits = t1.all | t1.some; + return (bits & t2.all) != 0; + } + + public static boolean isSameType(Context cx, SemType t1, SemType t2) { + return isSubType(cx, t1, t2) && isSubType(cx, t2, t1); + } + + public static BasicTypeBitSet widenToBasicTypes(SemType t) { + int all = t.all | t.some; + if (cardinality(all) > 1) { + throw new IllegalStateException("Cannot widen to basic type for a type with multiple basic types"); + } + return Builder.basicTypeUnion(all); + } + + private static int cardinality(int bitset) { + return Integer.bitCount(bitset); + } + + public static SemType widenToBasicTypeUnion(SemType t) { + if (t.some == 0) { + return t; + } + int all = t.all | t.some; + return Builder.basicTypeUnion(all); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemType.java new file mode 100644 index 000000000000..cc0f397adb1f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemType.java @@ -0,0 +1,76 @@ +/* + * 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. + */ + +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.internal.types.BSemTypeWrapper; +import io.ballerina.runtime.internal.types.semtype.PureSemType; + +/** + * Runtime representation of SemType. + * + * @since 2201.10.0 + */ +public abstract sealed class SemType implements BasicTypeBitSet permits BSemTypeWrapper, PureSemType { + + final int all; + final int some; + private final SubType[] subTypeData; + private static final SubType[] EMPTY_SUBTYPE_DATA = new SubType[0]; + + protected SemType(int all, int some, SubType[] subTypeData) { + this.all = all; + this.some = some; + this.subTypeData = subTypeData; + } + + protected SemType(int all) { + this.all = all; + this.some = 0; + this.subTypeData = EMPTY_SUBTYPE_DATA; + } + + protected SemType(SemType semType) { + this(semType.all(), semType.some(), semType.subTypeData()); + } + + public static SemType from(int all, int some, SubType[] subTypeData) { + return new PureSemType(all, some, subTypeData); + } + + public static SemType from(int all) { + return new PureSemType(all); + } + + @Override + public String toString() { + return SemTypeHelper.stringRepr(this); + } + + public final int all() { + return all; + } + + public final int some() { + return some; + } + + public final SubType[] subTypeData() { + return subTypeData; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemTypeHelper.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemTypeHelper.java new file mode 100644 index 000000000000..2f8376db1a4f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemTypeHelper.java @@ -0,0 +1,90 @@ +/* + * 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. + */ + +package io.ballerina.runtime.api.types.semtype; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_BOOLEAN; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_B_TYPE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_CELL; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_DECIMAL; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_ERROR; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_FLOAT; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_FUNCTION; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_FUTURE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_HANDLE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_INT; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_LIST; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_MAPPING; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_NIL; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_OBJECT; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_STREAM; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_STRING; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_TABLE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_TYPEDESC; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_UNDEF; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_XML; + +/** + * Collection of utility function on {@code SemType}. + * + * @since 2201.10.0 + */ +final class SemTypeHelper { + + private SemTypeHelper() { + } + + public static String stringRepr(SemType ty) { + return "all[" + bitSetRepr(ty.all()) + "] some [" + bitSetRepr(ty.some()) + "]"; + } + + private static String bitSetRepr(int bits) { + StringBuilder sb = new StringBuilder(); + appendBitSetRepr(sb, bits, CODE_NIL, "NIL"); + appendBitSetRepr(sb, bits, CODE_BOOLEAN, "BOOLEAN"); + appendBitSetRepr(sb, bits, CODE_INT, "INT"); + appendBitSetRepr(sb, bits, CODE_FLOAT, "FLOAT"); + appendBitSetRepr(sb, bits, CODE_DECIMAL, "DECIMAL"); + appendBitSetRepr(sb, bits, CODE_STRING, "STRING"); + appendBitSetRepr(sb, bits, CODE_ERROR, "ERROR"); + appendBitSetRepr(sb, bits, CODE_TYPEDESC, "TYPE_DESC"); + appendBitSetRepr(sb, bits, CODE_HANDLE, "HANDLE"); + appendBitSetRepr(sb, bits, CODE_FUNCTION, "FUNCTION"); + appendBitSetRepr(sb, bits, CODE_FUTURE, "FUTURE"); + appendBitSetRepr(sb, bits, CODE_STREAM, "STREAM"); + appendBitSetRepr(sb, bits, CODE_LIST, "LIST"); + appendBitSetRepr(sb, bits, CODE_MAPPING, "MAPPING"); + appendBitSetRepr(sb, bits, CODE_TABLE, "TABLE"); + appendBitSetRepr(sb, bits, CODE_XML, "XML"); + appendBitSetRepr(sb, bits, CODE_OBJECT, "OBJECT"); + appendBitSetRepr(sb, bits, CODE_CELL, "CELL"); + appendBitSetRepr(sb, bits, CODE_UNDEF, "UNDEF"); + appendBitSetRepr(sb, bits, CODE_B_TYPE, "B_TYPE"); + return sb.toString(); + } + + private static void appendBitSetRepr(StringBuilder sb, int bits, int index, String name) { + int mask = 1 << index; + if ((bits & mask) != 0) { + if (!sb.isEmpty()) { + sb.append(", "); + } + sb.append(name).append(" "); + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SubType.java new file mode 100644 index 000000000000..e8382dedae2b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SubType.java @@ -0,0 +1,59 @@ +/* + * 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. + */ + +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.internal.types.semtype.SubTypeData; + +/** + * Describe set of operation supported by each basic Type. + * + * @since 2201.10.0 + */ +public abstract class SubType { + + private final boolean all; + private final boolean nothing; + + protected SubType(boolean all, boolean nothing) { + this.all = all; + this.nothing = nothing; + } + + public abstract SubType union(SubType other); + + public abstract SubType intersect(SubType other); + + public SubType diff(SubType other) { + return this.intersect(other.complement()); + } + + public abstract SubType complement(); + + public abstract boolean isEmpty(); + + public final boolean isAll() { + return all; + } + + public final boolean isNothing() { + return nothing; + } + + public abstract SubTypeData data(); +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BString.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BString.java index 4450cda817ca..b7cd3dd8b87b 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BString.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BString.java @@ -17,6 +17,8 @@ */ package io.ballerina.runtime.api.values; +import io.ballerina.runtime.api.types.Type; + /** * Interface representing ballerina strings. * @@ -40,4 +42,5 @@ public interface BString { BIterator getIterator(); + Type getType(); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/FallbackTypeChecker.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/FallbackTypeChecker.java new file mode 100644 index 000000000000..eac629b1f6d0 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/FallbackTypeChecker.java @@ -0,0 +1,2371 @@ +/* + * 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. + */ + +package io.ballerina.runtime.internal; + +import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.flags.SymbolFlags; +import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.FunctionType; +import io.ballerina.runtime.api.types.IntersectionType; +import io.ballerina.runtime.api.types.MethodType; +import io.ballerina.runtime.api.types.ParameterizedType; +import io.ballerina.runtime.api.types.ReferenceType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.types.XmlNodeType; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BRefValue; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BXml; +import io.ballerina.runtime.internal.commons.TypeValuePair; +import io.ballerina.runtime.internal.types.BArrayType; +import io.ballerina.runtime.internal.types.BErrorType; +import io.ballerina.runtime.internal.types.BField; +import io.ballerina.runtime.internal.types.BFiniteType; +import io.ballerina.runtime.internal.types.BFunctionType; +import io.ballerina.runtime.internal.types.BFutureType; +import io.ballerina.runtime.internal.types.BIntersectionType; +import io.ballerina.runtime.internal.types.BJsonType; +import io.ballerina.runtime.internal.types.BMapType; +import io.ballerina.runtime.internal.types.BNetworkObjectType; +import io.ballerina.runtime.internal.types.BObjectType; +import io.ballerina.runtime.internal.types.BParameterizedType; +import io.ballerina.runtime.internal.types.BRecordType; +import io.ballerina.runtime.internal.types.BResourceMethodType; +import io.ballerina.runtime.internal.types.BStreamType; +import io.ballerina.runtime.internal.types.BTableType; +import io.ballerina.runtime.internal.types.BTupleType; +import io.ballerina.runtime.internal.types.BType; +import io.ballerina.runtime.internal.types.BTypeIdSet; +import io.ballerina.runtime.internal.types.BTypeReferenceType; +import io.ballerina.runtime.internal.types.BTypedescType; +import io.ballerina.runtime.internal.types.BUnionType; +import io.ballerina.runtime.internal.types.BXmlType; +import io.ballerina.runtime.internal.values.ArrayValue; +import io.ballerina.runtime.internal.values.DecimalValue; +import io.ballerina.runtime.internal.values.ErrorValue; +import io.ballerina.runtime.internal.values.MapValue; +import io.ballerina.runtime.internal.values.MapValueImpl; +import io.ballerina.runtime.internal.values.StreamValue; +import io.ballerina.runtime.internal.values.TableValueImpl; +import io.ballerina.runtime.internal.values.TupleValueImpl; +import io.ballerina.runtime.internal.values.XmlSequence; +import io.ballerina.runtime.internal.values.XmlValue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static io.ballerina.runtime.api.PredefinedTypes.TYPE_ANY; +import static io.ballerina.runtime.api.PredefinedTypes.TYPE_ANYDATA; +import static io.ballerina.runtime.api.PredefinedTypes.TYPE_JSON; +import static io.ballerina.runtime.api.PredefinedTypes.TYPE_READONLY_JSON; +import static io.ballerina.runtime.api.utils.TypeUtils.getImpliedType; +import static io.ballerina.runtime.api.utils.TypeUtils.isValueType; +import static io.ballerina.runtime.internal.TypeConverter.ERROR_MESSAGE_UNION_END; +import static io.ballerina.runtime.internal.TypeConverter.ERROR_MESSAGE_UNION_SEPARATOR; +import static io.ballerina.runtime.internal.TypeConverter.ERROR_MESSAGE_UNION_START; + +// Contains all the existing non semtype type check logic, SEMTYPE-TODO: remove this once semtype implementation is +// complete +final class FallbackTypeChecker { + + static final byte MAX_TYPECAST_ERROR_COUNT = 20; + + private FallbackTypeChecker() { + } + + static boolean checkIsType(List errors, Object sourceVal, BType sourceType, BType targetType) { + if (TypeChecker.checkIsType(sourceVal, sourceType, targetType, null)) { + return true; + } + + if (getImpliedType(sourceType).getTag() == TypeTags.XML_TAG && !targetType.isReadOnly()) { + XmlValue val = (XmlValue) sourceVal; + if (val.getNodeType() == XmlNodeType.SEQUENCE) { + return checkIsLikeOnValue(errors, sourceVal, sourceType, targetType, new ArrayList<>(), + false, null); + } + } + + if (isMutable(sourceVal, sourceType)) { + return false; + } + + return checkIsLikeOnValue(errors, sourceVal, sourceType, targetType, new ArrayList<>(), false, + null); + } + + @Deprecated + static boolean checkIsType(BType sourceType, BType targetType, List unresolvedTypes) { + // First check whether both types are the same. + if (sourceType == targetType || (sourceType.getTag() == targetType.getTag() && sourceType.equals(targetType))) { + return true; + } + + if (checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(sourceType)) { + return true; + } + + if (targetType.isReadOnly() && !sourceType.isReadOnly()) { + return false; + } + + int sourceTypeTag = sourceType.getTag(); + int targetTypeTag = targetType.getTag(); + + switch (sourceTypeTag) { + case TypeTags.INTERSECTION_TAG: + return TypeChecker.checkIsType(((IntersectionType) sourceType).getEffectiveType(), + targetTypeTag != TypeTags.INTERSECTION_TAG ? targetType : + ((IntersectionType) targetType).getEffectiveType(), unresolvedTypes); + case TypeTags.TYPE_REFERENCED_TYPE_TAG: + return TypeChecker.checkIsType(((ReferenceType) sourceType).getReferredType(), + targetTypeTag != TypeTags.TYPE_REFERENCED_TYPE_TAG ? targetType : + ((ReferenceType) targetType).getReferredType(), unresolvedTypes); + case TypeTags.PARAMETERIZED_TYPE_TAG: + if (targetTypeTag != TypeTags.PARAMETERIZED_TYPE_TAG) { + return TypeChecker.checkIsType(((ParameterizedType) sourceType).getParamValueType(), targetType, + unresolvedTypes); + } + return TypeChecker.checkIsType(((ParameterizedType) sourceType).getParamValueType(), + ((ParameterizedType) targetType).getParamValueType(), unresolvedTypes); + case TypeTags.READONLY_TAG: + return TypeChecker.checkIsType(PredefinedTypes.ANY_AND_READONLY_OR_ERROR_TYPE, + targetType, unresolvedTypes); + case TypeTags.UNION_TAG: + return isUnionTypeMatch((BUnionType) sourceType, targetType, unresolvedTypes); + case TypeTags.FINITE_TYPE_TAG: + if ((targetTypeTag == TypeTags.FINITE_TYPE_TAG || targetTypeTag <= TypeTags.NULL_TAG || + targetTypeTag == TypeTags.XML_TEXT_TAG)) { + return isFiniteTypeMatch((BFiniteType) sourceType, targetType); + } + break; + default: + break; + } + + return switch (targetTypeTag) { + case TypeTags.BYTE_TAG, TypeTags.SIGNED8_INT_TAG, TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, + TypeTags.CHAR_STRING_TAG, TypeTags.BOOLEAN_TAG, TypeTags.NULL_TAG -> sourceTypeTag == targetTypeTag; + case TypeTags.STRING_TAG -> TypeTags.isStringTypeTag(sourceTypeTag); + case TypeTags.XML_TEXT_TAG -> { + if (sourceTypeTag == TypeTags.XML_TAG) { + yield ((BXmlType) sourceType).constraint.getTag() == TypeTags.NEVER_TAG; + } + yield sourceTypeTag == targetTypeTag; + } + case TypeTags.INT_TAG -> sourceTypeTag == TypeTags.INT_TAG || sourceTypeTag == TypeTags.BYTE_TAG || + (sourceTypeTag >= TypeTags.SIGNED8_INT_TAG && sourceTypeTag <= TypeTags.UNSIGNED32_INT_TAG); + case TypeTags.SIGNED16_INT_TAG -> sourceTypeTag == TypeTags.BYTE_TAG || + (sourceTypeTag >= TypeTags.SIGNED8_INT_TAG && sourceTypeTag <= TypeTags.SIGNED16_INT_TAG); + case TypeTags.SIGNED32_INT_TAG -> sourceTypeTag == TypeTags.BYTE_TAG || + (sourceTypeTag >= TypeTags.SIGNED8_INT_TAG && sourceTypeTag <= TypeTags.SIGNED32_INT_TAG); + case TypeTags.UNSIGNED8_INT_TAG -> + sourceTypeTag == TypeTags.BYTE_TAG || sourceTypeTag == TypeTags.UNSIGNED8_INT_TAG; + case TypeTags.UNSIGNED16_INT_TAG -> + sourceTypeTag == TypeTags.BYTE_TAG || sourceTypeTag == TypeTags.UNSIGNED8_INT_TAG || + sourceTypeTag == TypeTags.UNSIGNED16_INT_TAG; + case TypeTags.UNSIGNED32_INT_TAG -> + sourceTypeTag == TypeTags.BYTE_TAG || sourceTypeTag == TypeTags.UNSIGNED8_INT_TAG || + sourceTypeTag == TypeTags.UNSIGNED16_INT_TAG || + sourceTypeTag == TypeTags.UNSIGNED32_INT_TAG; + case TypeTags.ANY_TAG -> checkIsAnyType(sourceType); + case TypeTags.ANYDATA_TAG -> sourceType.isAnydata(); + case TypeTags.SERVICE_TAG -> checkIsServiceType(sourceType, targetType, + unresolvedTypes == null ? new ArrayList<>() : unresolvedTypes); + case TypeTags.HANDLE_TAG -> sourceTypeTag == TypeTags.HANDLE_TAG; + case TypeTags.READONLY_TAG -> + TypeChecker.checkIsType(sourceType, PredefinedTypes.ANY_AND_READONLY_OR_ERROR_TYPE, + unresolvedTypes); + case TypeTags.XML_ELEMENT_TAG, TypeTags.XML_COMMENT_TAG, TypeTags.XML_PI_TAG -> + targetTypeTag == sourceTypeTag; + case TypeTags.INTERSECTION_TAG -> + TypeChecker.checkIsType(sourceType, ((IntersectionType) targetType).getEffectiveType(), + unresolvedTypes); + case TypeTags.TYPE_REFERENCED_TYPE_TAG -> + TypeChecker.checkIsType(sourceType, ((ReferenceType) targetType).getReferredType(), + unresolvedTypes); + default -> checkIsRecursiveType(sourceType, targetType, + unresolvedTypes == null ? new ArrayList<>() : unresolvedTypes); + }; + } + + static boolean checkIsType(Object sourceVal, BType sourceBType, BType targetBType, + List unresolvedTypes) { + Type sourceType = getImpliedType(sourceBType); + Type targetType = getImpliedType(targetBType); + + int sourceTypeTag = sourceType.getTag(); + int targetTypeTag = targetType.getTag(); + + // If the source type is neither a record type nor an object type, check `is` type by looking only at the types. + // Else, since records and objects may have `readonly` or `final` fields, need to use the value also. + // e.g., + // const HUNDRED = 100; + // + // type Foo record { + // HUNDRED i; + // }; + // + // type Bar record { + // readonly string|int i; + // }; + // + // where `Bar b = {i: 100};`, `b is Foo` should evaluate to true. + if (sourceTypeTag != TypeTags.RECORD_TYPE_TAG && sourceTypeTag != TypeTags.OBJECT_TYPE_TAG) { + return TypeChecker.checkIsType(sourceType, targetType); + } + + if (sourceType == targetType || (sourceType.getTag() == targetType.getTag() && sourceType.equals(targetType))) { + return true; + } + + if (targetType.isReadOnly() && !sourceType.isReadOnly()) { + return false; + } + + return switch (targetTypeTag) { + case TypeTags.ANY_TAG -> checkIsAnyType(sourceType); + case TypeTags.READONLY_TAG -> TypeChecker.isInherentlyImmutableType(sourceType) || sourceType.isReadOnly(); + default -> checkIsRecursiveTypeOnValue(sourceVal, sourceType, targetType, sourceTypeTag, + targetTypeTag, unresolvedTypes == null ? new ArrayList<>() : unresolvedTypes); + }; + } + + /** + * Checks if the given decimal number is a real number. + * + * @param decimalValue The decimal value being checked + * @return True if the decimal value is a real number. + */ + static boolean isDecimalRealNumber(DecimalValue decimalValue) { + return decimalValue.valueKind == DecimalValueKind.ZERO || decimalValue.valueKind == DecimalValueKind.OTHER; + } + + static boolean isFiniteTypeMatch(BFiniteType sourceType, Type targetType) { + for (Object bValue : sourceType.valueSpace) { + if (!TypeChecker.checkIsType(bValue, targetType)) { + return false; + } + } + return true; + } + + static boolean isUnionTypeMatch(BUnionType sourceType, Type targetType, + List unresolvedTypes) { + for (Type type : sourceType.getMemberTypes()) { + if (!TypeChecker.checkIsType(type, targetType, unresolvedTypes)) { + return false; + } + } + return true; + } + + static boolean hasIncompatibleReadOnlyFlags(Field targetField, Field sourceField) { + return SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.READONLY) && !SymbolFlags + .isFlagOn(sourceField.getFlags(), + SymbolFlags.READONLY); + } + + static boolean checkIsAnyType(Type sourceType) { + sourceType = getImpliedType(sourceType); + switch (sourceType.getTag()) { + case TypeTags.ERROR_TAG: + case TypeTags.READONLY_TAG: + return false; + case TypeTags.UNION_TAG: + case TypeTags.ANYDATA_TAG: + case TypeTags.JSON_TAG: + for (Type memberType : ((BUnionType) sourceType).getMemberTypes()) { + if (!checkIsAnyType(memberType)) { + return false; + } + } + return true; + default: + return true; + } + } + + static boolean checkObjectEquivalency(Type sourceType, BObjectType targetType, + List unresolvedTypes) { + return checkObjectEquivalency(null, sourceType, targetType, unresolvedTypes); + } + + static boolean checkObjectEquivalency(Object sourceVal, Type sourceType, BObjectType targetType, + List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + if (sourceType.getTag() != TypeTags.OBJECT_TYPE_TAG && sourceType.getTag() != TypeTags.SERVICE_TAG) { + return false; + } + // If we encounter two types that we are still resolving, then skip it. + // This is done to avoid recursive checking of the same type. + TypeChecker.TypePair pair = new TypeChecker.TypePair(sourceType, targetType); + if (unresolvedTypes.contains(pair)) { + return true; + } + unresolvedTypes.add(pair); + + BObjectType sourceObjectType = (BObjectType) sourceType; + + if (SymbolFlags.isFlagOn(targetType.flags, SymbolFlags.ISOLATED) && + !SymbolFlags.isFlagOn(sourceObjectType.flags, SymbolFlags.ISOLATED)) { + return false; + } + + Map targetFields = targetType.getFields(); + Map sourceFields = sourceObjectType.getFields(); + List targetFuncs = getAllFunctionsList(targetType); + List sourceFuncs = getAllFunctionsList(sourceObjectType); + + if (targetType.getFields().values().stream().anyMatch(field -> SymbolFlags + .isFlagOn(field.getFlags(), SymbolFlags.PRIVATE)) + || targetFuncs.stream().anyMatch(func -> SymbolFlags.isFlagOn(func.getFlags(), + SymbolFlags.PRIVATE))) { + return false; + } + + if (targetFields.size() > sourceFields.size() || targetFuncs.size() > sourceFuncs.size()) { + return false; + } + + String targetTypeModule = Optional.ofNullable(targetType.getPackage()).map(Module::toString).orElse(""); + String sourceTypeModule = Optional.ofNullable(sourceObjectType.getPackage()).map(Module::toString).orElse(""); + + if (sourceVal == null) { + if (!checkObjectSubTypeForFields(targetFields, sourceFields, targetTypeModule, sourceTypeModule, + unresolvedTypes)) { + return false; + } + } else if (!checkObjectSubTypeForFieldsByValue(targetFields, sourceFields, targetTypeModule, sourceTypeModule, + (BObject) sourceVal, unresolvedTypes)) { + return false; + } + + return checkObjectSubTypeForMethods(unresolvedTypes, targetFuncs, sourceFuncs, targetTypeModule, + sourceTypeModule, sourceObjectType, targetType); + } + + private static List getAllFunctionsList(BObjectType objectType) { + List functionList = new ArrayList<>(Arrays.asList(objectType.getMethods())); + if (objectType.getTag() == TypeTags.SERVICE_TAG || + (objectType.flags & SymbolFlags.CLIENT) == SymbolFlags.CLIENT) { + Collections.addAll(functionList, ((BNetworkObjectType) objectType).getResourceMethods()); + } + + return functionList; + } + + private static boolean checkObjectSubTypeForFields(Map targetFields, + Map sourceFields, String targetTypeModule, + String sourceTypeModule, + List unresolvedTypes) { + for (Field lhsField : targetFields.values()) { + Field rhsField = sourceFields.get(lhsField.getFieldName()); + if (rhsField == null || + !isInSameVisibilityRegion(targetTypeModule, sourceTypeModule, lhsField.getFlags(), + rhsField.getFlags()) || hasIncompatibleReadOnlyFlags(lhsField, + rhsField) || + !TypeChecker.checkIsType(rhsField.getFieldType(), lhsField.getFieldType(), unresolvedTypes)) { + return false; + } + } + return true; + } + + private static boolean checkObjectSubTypeForFieldsByValue(Map targetFields, + Map sourceFields, String targetTypeModule, + String sourceTypeModule, BObject sourceObjVal, + List unresolvedTypes) { + for (Field lhsField : targetFields.values()) { + String name = lhsField.getFieldName(); + Field rhsField = sourceFields.get(name); + if (rhsField == null || + !isInSameVisibilityRegion(targetTypeModule, sourceTypeModule, lhsField.getFlags(), + rhsField.getFlags()) || hasIncompatibleReadOnlyFlags(lhsField, + rhsField)) { + return false; + } + + if (SymbolFlags.isFlagOn(rhsField.getFlags(), SymbolFlags.FINAL)) { + Object fieldValue = sourceObjVal.get(StringUtils.fromString(name)); + Type fieldValueType = TypeChecker.getType(fieldValue); + + if (fieldValueType.isReadOnly()) { + if (!TypeChecker.checkIsLikeType(fieldValue, lhsField.getFieldType())) { + return false; + } + continue; + } + + if (!TypeChecker.checkIsType(fieldValueType, lhsField.getFieldType(), unresolvedTypes)) { + return false; + } + } else if (!TypeChecker.checkIsType(rhsField.getFieldType(), lhsField.getFieldType(), unresolvedTypes)) { + return false; + } + } + return true; + } + + private static boolean checkObjectSubTypeForMethods(List unresolvedTypes, + List targetFuncs, + List sourceFuncs, + String targetTypeModule, String sourceTypeModule, + BObjectType sourceType, BObjectType targetType) { + for (MethodType lhsFunc : targetFuncs) { + Optional rhsFunction = getMatchingInvokableType(sourceFuncs, lhsFunc, unresolvedTypes); + if (rhsFunction.isEmpty()) { + return false; + } + + MethodType rhsFunc = rhsFunction.get(); + if (rhsFunc == null || + !isInSameVisibilityRegion(targetTypeModule, sourceTypeModule, lhsFunc.getFlags(), + rhsFunc.getFlags())) { + return false; + } + if (SymbolFlags.isFlagOn(lhsFunc.getFlags(), SymbolFlags.REMOTE) != SymbolFlags + .isFlagOn(rhsFunc.getFlags(), SymbolFlags.REMOTE)) { + return false; + } + } + + // Target type is not a distinct type, no need to match type-ids + BTypeIdSet targetTypeIdSet = targetType.typeIdSet; + if (targetTypeIdSet == null) { + return true; + } + + BTypeIdSet sourceTypeIdSet = sourceType.typeIdSet; + if (sourceTypeIdSet == null) { + return false; + } + + return sourceTypeIdSet.containsAll(targetTypeIdSet); + } + + private static boolean isInSameVisibilityRegion(String lhsTypePkg, String rhsTypePkg, long lhsFlags, + long rhsFlags) { + if (SymbolFlags.isFlagOn(lhsFlags, SymbolFlags.PRIVATE)) { + return lhsTypePkg.equals(rhsTypePkg); + } else if (SymbolFlags.isFlagOn(lhsFlags, SymbolFlags.PUBLIC)) { + return SymbolFlags.isFlagOn(rhsFlags, SymbolFlags.PUBLIC); + } + return !SymbolFlags.isFlagOn(rhsFlags, SymbolFlags.PRIVATE) && !SymbolFlags + .isFlagOn(rhsFlags, SymbolFlags.PUBLIC) && + lhsTypePkg.equals(rhsTypePkg); + } + + private static Optional getMatchingInvokableType(List rhsFuncs, + MethodType lhsFunc, + List unresolvedTypes) { + Optional matchingFunction = rhsFuncs.stream() + .filter(rhsFunc -> lhsFunc.getName().equals(rhsFunc.getName())) + .filter(rhsFunc -> checkFunctionTypeEqualityForObjectType(rhsFunc.getType(), lhsFunc.getType(), + unresolvedTypes)) + .findFirst(); + + if (matchingFunction.isEmpty()) { + return matchingFunction; + } + // For resource function match, we need to check whether lhs function resource path type belongs to + // rhs function resource path type + MethodType matchingFunc = matchingFunction.get(); + boolean lhsFuncIsResource = SymbolFlags.isFlagOn(lhsFunc.getFlags(), SymbolFlags.RESOURCE); + boolean matchingFuncIsResource = SymbolFlags.isFlagOn(matchingFunc.getFlags(), SymbolFlags.RESOURCE); + + if (!lhsFuncIsResource && !matchingFuncIsResource) { + return matchingFunction; + } + + if ((lhsFuncIsResource && !matchingFuncIsResource) || (matchingFuncIsResource && !lhsFuncIsResource)) { + return Optional.empty(); + } + + Type[] lhsFuncResourcePathTypes = ((BResourceMethodType) lhsFunc).pathSegmentTypes; + Type[] rhsFuncResourcePathTypes = ((BResourceMethodType) matchingFunc).pathSegmentTypes; + + int lhsFuncResourcePathTypesSize = lhsFuncResourcePathTypes.length; + if (lhsFuncResourcePathTypesSize != rhsFuncResourcePathTypes.length) { + return Optional.empty(); + } + + for (int i = 0; i < lhsFuncResourcePathTypesSize; i++) { + if (!TypeChecker.checkIsType(lhsFuncResourcePathTypes[i], rhsFuncResourcePathTypes[i])) { + return Optional.empty(); + } + } + + return matchingFunction; + } + + private static boolean checkFunctionTypeEqualityForObjectType(FunctionType source, FunctionType target, + List unresolvedTypes) { + if (hasIncompatibleIsolatedFlags(target, source)) { + return false; + } + + if (source.getParameters().length != target.getParameters().length) { + return false; + } + + for (int i = 0; i < source.getParameters().length; i++) { + if (!TypeChecker.checkIsType(target.getParameters()[i].type, source.getParameters()[i].type, + unresolvedTypes)) { + return false; + } + } + + if (source.getReturnType() == null && target.getReturnType() == null) { + return true; + } else if (source.getReturnType() == null || target.getReturnType() == null) { + return false; + } + + return TypeChecker.checkIsType(source.getReturnType(), target.getReturnType(), unresolvedTypes); + } + + static boolean hasIncompatibleIsolatedFlags(FunctionType target, FunctionType source) { + return SymbolFlags.isFlagOn(target.getFlags(), SymbolFlags.ISOLATED) && !SymbolFlags + .isFlagOn(source.getFlags(), SymbolFlags.ISOLATED); + } + + static boolean checkIsServiceType(Type sourceType, Type targetType, List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + if (sourceType.getTag() == TypeTags.SERVICE_TAG) { + return checkObjectEquivalency(sourceType, (BObjectType) targetType, unresolvedTypes); + } + + if (sourceType.getTag() == TypeTags.OBJECT_TYPE_TAG) { + var flags = ((BObjectType) sourceType).flags; + return (flags & SymbolFlags.SERVICE) == SymbolFlags.SERVICE; + } + + return false; + } + + static boolean isMutable(Object value, Type sourceType) { + // All the value types are immutable + sourceType = getImpliedType(sourceType); + if (value == null || sourceType.getTag() < TypeTags.NULL_TAG || + sourceType.getTag() == TypeTags.FINITE_TYPE_TAG) { + return false; + } + + return !((BRefValue) value).isFrozen(); + } + + static boolean checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(Type type) { + Set visitedTypeSet = new HashSet<>(); + return checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(type, visitedTypeSet); + } + + private static boolean checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(Type type, + Set visitedTypeSet) { + switch (type.getTag()) { + case TypeTags.NEVER_TAG: + return true; + case TypeTags.RECORD_TYPE_TAG: + BRecordType recordType = (BRecordType) type; + visitedTypeSet.add(recordType.getName()); + for (Field field : recordType.getFields().values()) { + // skip check for fields with self referencing type and not required fields. + if ((SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED) || + !SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL)) && + !visitedTypeSet.contains(field.getFieldType()) && + checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(field.getFieldType(), + visitedTypeSet)) { + return true; + } + } + return false; + case TypeTags.TUPLE_TAG: + BTupleType tupleType = (BTupleType) type; + visitedTypeSet.add(tupleType.getName()); + List tupleTypes = tupleType.getTupleTypes(); + for (Type mem : tupleTypes) { + if (!visitedTypeSet.add(mem.getName())) { + continue; + } + if (checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(mem, visitedTypeSet)) { + return true; + } + } + return false; + case TypeTags.ARRAY_TAG: + BArrayType arrayType = (BArrayType) type; + visitedTypeSet.add(arrayType.getName()); + Type elemType = arrayType.getElementType(); + visitedTypeSet.add(elemType.getName()); + return arrayType.getState() != ArrayType.ArrayState.OPEN && + checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(elemType, visitedTypeSet); + case TypeTags.TYPE_REFERENCED_TYPE_TAG: + return checkIsNeverTypeOrStructureTypeWithARequiredNeverMember( + ((BTypeReferenceType) type).getReferredType(), visitedTypeSet); + case TypeTags.INTERSECTION_TAG: + return checkIsNeverTypeOrStructureTypeWithARequiredNeverMember( + ((BIntersectionType) type).getEffectiveType(), visitedTypeSet); + default: + return false; + } + } + + /** + * Check whether a given value confirms to a given type. First it checks if the type of the value, and if fails then + * falls back to checking the value. + * + * @param errors list to collect typecast errors + * @param sourceValue Value to check + * @param targetType Target type + * @param unresolvedValues Values that are unresolved so far + * @param allowNumericConversion Flag indicating whether to perform numeric conversions + * @param varName variable name to identify the parent of a record field + * @return True if the value confirms to the provided type. False, otherwise. + */ + static boolean checkIsLikeType(List errors, Object sourceValue, Type targetType, + List unresolvedValues, boolean allowNumericConversion, + String varName) { + Type sourceType = TypeChecker.getType(sourceValue); + if (TypeChecker.checkIsType(sourceType, targetType, new ArrayList<>())) { + return true; + } + + return checkIsLikeOnValue(errors, sourceValue, sourceType, targetType, unresolvedValues, + allowNumericConversion, varName); + } + + /** + * Check whether a given value confirms to a given type. Strictly checks the value only, and does not consider the + * type of the value for consideration. + * + * @param errors list to collect typecast errors + * @param sourceValue Value to check + * @param sourceType Type of the value + * @param targetType Target type + * @param unresolvedValues Values that are unresolved so far + * @param allowNumericConversion Flag indicating whether to perform numeric conversions + * @param varName variable name to identify the parent of a record field + * @return True if the value confirms to the provided type. False, otherwise. + */ + static boolean checkIsLikeOnValue(List errors, Object sourceValue, Type sourceType, Type targetType, + List unresolvedValues, boolean allowNumericConversion, + String varName) { + int sourceTypeTag = sourceType.getTag(); + int targetTypeTag = targetType.getTag(); + + switch (sourceTypeTag) { + case TypeTags.INTERSECTION_TAG: + return checkIsLikeOnValue(errors, sourceValue, ((BIntersectionType) sourceType).getEffectiveType(), + targetTypeTag != TypeTags.INTERSECTION_TAG ? targetType : + ((BIntersectionType) targetType).getEffectiveType(), + unresolvedValues, allowNumericConversion, varName); + case TypeTags.PARAMETERIZED_TYPE_TAG: + if (targetTypeTag != TypeTags.PARAMETERIZED_TYPE_TAG) { + return checkIsLikeOnValue(errors, sourceValue, + ((BParameterizedType) sourceType).getParamValueType(), targetType, unresolvedValues, + allowNumericConversion, varName); + } + return checkIsLikeOnValue(errors, sourceValue, ((BParameterizedType) sourceType).getParamValueType(), + ((BParameterizedType) targetType).getParamValueType(), unresolvedValues, + allowNumericConversion, varName); + default: + break; + } + + switch (targetTypeTag) { + case TypeTags.READONLY_TAG: + return true; + case TypeTags.BYTE_TAG: + if (TypeTags.isIntegerTypeTag(sourceTypeTag)) { + return TypeChecker.isByteLiteral(((Number) sourceValue).longValue()); + } + return allowNumericConversion && TypeConverter.isConvertibleToByte(sourceValue); + case TypeTags.INT_TAG: + return allowNumericConversion && TypeConverter.isConvertibleToInt(sourceValue); + case TypeTags.SIGNED32_INT_TAG: + case TypeTags.SIGNED16_INT_TAG: + case TypeTags.SIGNED8_INT_TAG: + case TypeTags.UNSIGNED32_INT_TAG: + case TypeTags.UNSIGNED16_INT_TAG: + case TypeTags.UNSIGNED8_INT_TAG: + if (TypeTags.isIntegerTypeTag(sourceTypeTag)) { + return TypeConverter.isConvertibleToIntSubType(sourceValue, targetType); + } + return allowNumericConversion && TypeConverter.isConvertibleToIntSubType(sourceValue, targetType); + case TypeTags.FLOAT_TAG: + case TypeTags.DECIMAL_TAG: + return allowNumericConversion && TypeConverter.isConvertibleToFloatingPointTypes(sourceValue); + case TypeTags.CHAR_STRING_TAG: + return TypeConverter.isConvertibleToChar(sourceValue); + case TypeTags.RECORD_TYPE_TAG: + return checkIsLikeRecordType(sourceValue, (BRecordType) targetType, unresolvedValues, + allowNumericConversion, varName, errors); + case TypeTags.TABLE_TAG: + return checkIsLikeTableType(sourceValue, (BTableType) targetType, unresolvedValues, + allowNumericConversion); + case TypeTags.JSON_TAG: + return checkIsLikeJSONType(sourceValue, sourceType, (BJsonType) targetType, unresolvedValues, + allowNumericConversion); + case TypeTags.MAP_TAG: + return checkIsLikeMapType(sourceValue, (BMapType) targetType, unresolvedValues, allowNumericConversion); + case TypeTags.STREAM_TAG: + return checkIsLikeStreamType(sourceValue, (BStreamType) targetType); + case TypeTags.ARRAY_TAG: + return checkIsLikeArrayType(sourceValue, (BArrayType) targetType, unresolvedValues, + allowNumericConversion); + case TypeTags.TUPLE_TAG: + return checkIsLikeTupleType(sourceValue, (BTupleType) targetType, unresolvedValues, + allowNumericConversion); + case TypeTags.ERROR_TAG: + return checkIsLikeErrorType(sourceValue, (BErrorType) targetType, unresolvedValues, + allowNumericConversion); + case TypeTags.ANYDATA_TAG: + return checkIsLikeAnydataType(sourceValue, sourceType, unresolvedValues, allowNumericConversion); + case TypeTags.FINITE_TYPE_TAG: + return checkFiniteTypeAssignable(sourceValue, sourceType, (BFiniteType) targetType, + unresolvedValues, allowNumericConversion); + case TypeTags.XML_ELEMENT_TAG: + case TypeTags.XML_COMMENT_TAG: + case TypeTags.XML_PI_TAG: + case TypeTags.XML_TEXT_TAG: + if (TypeTags.isXMLTypeTag(sourceTypeTag)) { + return checkIsLikeXmlValueSingleton((XmlValue) sourceValue, targetType); + } + return false; + case TypeTags.XML_TAG: + if (TypeTags.isXMLTypeTag(sourceTypeTag)) { + return checkIsLikeXMLSequenceType((XmlValue) sourceValue, targetType); + } + return false; + case TypeTags.UNION_TAG: + return checkIsLikeUnionType(errors, sourceValue, (BUnionType) targetType, unresolvedValues, + allowNumericConversion, varName); + case TypeTags.INTERSECTION_TAG: + return checkIsLikeOnValue(errors, sourceValue, sourceType, + ((BIntersectionType) targetType).getEffectiveType(), unresolvedValues, allowNumericConversion, + varName); + case TypeTags.TYPE_REFERENCED_TYPE_TAG: + return checkIsLikeOnValue(errors, sourceValue, sourceType, + ((BTypeReferenceType) targetType).getReferredType(), unresolvedValues, allowNumericConversion, + varName); + default: + return false; + } + } + + private static boolean checkIsLikeUnionType(List errors, Object sourceValue, BUnionType targetType, + List unresolvedValues, boolean allowNumericConversion, + String varName) { + if (allowNumericConversion) { + List compatibleTypesWithNumConversion = new ArrayList<>(); + List compatibleTypesWithoutNumConversion = new ArrayList<>(); + for (Type type : targetType.getMemberTypes()) { + List tempList = new ArrayList<>(unresolvedValues.size()); + tempList.addAll(unresolvedValues); + + if (checkIsLikeType(null, sourceValue, type, tempList, false, varName)) { + compatibleTypesWithoutNumConversion.add(type); + } + + if (checkIsLikeType(null, sourceValue, type, unresolvedValues, true, varName)) { + compatibleTypesWithNumConversion.add(type); + } + } + // Conversion should only be possible to one other numeric type. + return !compatibleTypesWithNumConversion.isEmpty() && + compatibleTypesWithNumConversion.size() - compatibleTypesWithoutNumConversion.size() <= 1; + } else { + return checkIsLikeUnionType(errors, sourceValue, targetType, unresolvedValues, varName); + } + } + + private static boolean checkIsLikeUnionType(List errors, Object sourceValue, BUnionType targetType, + List unresolvedValues, String varName) { + if (errors == null) { + for (Type type : targetType.getMemberTypes()) { + if (checkIsLikeType(null, sourceValue, type, unresolvedValues, false, varName)) { + return true; + } + } + } else { + int initialErrorCount; + errors.add(ERROR_MESSAGE_UNION_START); + int initialErrorListSize = errors.size(); + for (Type type : targetType.getMemberTypes()) { + initialErrorCount = errors.size(); + if (checkIsLikeType(errors, sourceValue, type, unresolvedValues, false, varName)) { + errors.subList(initialErrorListSize - 1, errors.size()).clear(); + return true; + } + if (initialErrorCount != errors.size()) { + errors.add(ERROR_MESSAGE_UNION_SEPARATOR); + } + } + int currentErrorListSize = errors.size(); + errors.remove(currentErrorListSize - 1); + if (initialErrorListSize != currentErrorListSize) { + errors.add(ERROR_MESSAGE_UNION_END); + } + } + return false; + } + + private static XmlNodeType getXmlNodeType(Type type) { + switch (getImpliedType(type).getTag()) { + case TypeTags.XML_ELEMENT_TAG: + return XmlNodeType.ELEMENT; + case TypeTags.XML_COMMENT_TAG: + return XmlNodeType.COMMENT; + case TypeTags.XML_PI_TAG: + return XmlNodeType.PI; + default: + return XmlNodeType.TEXT; + } + } + + private static boolean checkIsLikeXmlValueSingleton(XmlValue xmlSource, Type targetType) { + XmlNodeType targetXmlNodeType = getXmlNodeType(targetType); + XmlNodeType xmlSourceNodeType = xmlSource.getNodeType(); + + if (xmlSourceNodeType == targetXmlNodeType) { + return true; + } + + if (xmlSourceNodeType == XmlNodeType.SEQUENCE) { + XmlSequence seq = (XmlSequence) xmlSource; + return seq.size() == 1 && seq.getChildrenList().get(0).getNodeType() == targetXmlNodeType || + (targetXmlNodeType == XmlNodeType.TEXT && seq.isEmpty()); + } + + return false; + } + + private static void populateTargetXmlNodeTypes(Set nodeTypes, Type targetType) { + // there are only 4 xml subtypes + if (nodeTypes.size() == 4) { + return; + } + + Type referredType = getImpliedType(targetType); + switch (referredType.getTag()) { + case TypeTags.UNION_TAG: + for (Type memberType : ((UnionType) referredType).getMemberTypes()) { + populateTargetXmlNodeTypes(nodeTypes, memberType); + } + break; + case TypeTags.INTERSECTION_TAG: + populateTargetXmlNodeTypes(nodeTypes, ((IntersectionType) referredType).getEffectiveType()); + break; + case TypeTags.XML_ELEMENT_TAG: + nodeTypes.add(XmlNodeType.ELEMENT); + break; + case TypeTags.XML_COMMENT_TAG: + nodeTypes.add(XmlNodeType.COMMENT); + break; + case TypeTags.XML_PI_TAG: + nodeTypes.add(XmlNodeType.PI); + break; + case TypeTags.XML_TEXT_TAG: + nodeTypes.add(XmlNodeType.TEXT); + break; + case TypeTags.XML_TAG: + populateTargetXmlNodeTypes(nodeTypes, ((BXmlType) referredType).constraint); + break; + default: + break; + + } + } + + private static boolean checkIsLikeXMLSequenceType(XmlValue xmlSource, Type targetType) { + Set acceptedNodeTypes = new HashSet<>(); + populateTargetXmlNodeTypes(acceptedNodeTypes, targetType); + + XmlNodeType xmlSourceNodeType = xmlSource.getNodeType(); + if (xmlSourceNodeType != XmlNodeType.SEQUENCE) { + return acceptedNodeTypes.contains(xmlSourceNodeType); + } + + XmlSequence seq = (XmlSequence) xmlSource; + for (BXml m : seq.getChildrenList()) { + if (!acceptedNodeTypes.contains(m.getNodeType())) { + return false; + } + } + return true; + } + + private static boolean checkIsLikeAnydataType(Object sourceValue, Type sourceType, + List unresolvedValues, + boolean allowNumericConversion) { + sourceType = getImpliedType(sourceType); + switch (sourceType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: + case TypeTags.MAP_TAG: + return isLikeAnydataType(((MapValueImpl) sourceValue).values().toArray(), + unresolvedValues, allowNumericConversion); + case TypeTags.TABLE_TAG: + return isLikeAnydataType(((TableValueImpl) sourceValue).values().toArray(), + unresolvedValues, allowNumericConversion); + case TypeTags.ARRAY_TAG: + ArrayValue arr = (ArrayValue) sourceValue; + BArrayType arrayType = (BArrayType) getImpliedType(arr.getType()); + switch (getImpliedType(arrayType.getElementType()).getTag()) { + case TypeTags.INT_TAG: + case TypeTags.FLOAT_TAG: + case TypeTags.DECIMAL_TAG: + case TypeTags.STRING_TAG: + case TypeTags.BOOLEAN_TAG: + case TypeTags.BYTE_TAG: + return true; + default: + return isLikeAnydataType(arr.getValues(), unresolvedValues, allowNumericConversion); + } + case TypeTags.TUPLE_TAG: + return isLikeAnydataType(((ArrayValue) sourceValue).getValues(), unresolvedValues, + allowNumericConversion); + default: + return sourceType.isAnydata(); + } + } + + private static boolean isLikeAnydataType(Object[] objects, List unresolvedValues, + boolean allowNumericConversion) { + for (Object value : objects) { + if (!checkIsLikeType(null, value, TYPE_ANYDATA, unresolvedValues, allowNumericConversion, + null)) { + return false; + } + } + return true; + } + + private static boolean checkIsLikeTupleType(Object sourceValue, BTupleType targetType, + List unresolvedValues, boolean allowNumericConversion) { + if (!(sourceValue instanceof ArrayValue source)) { + return false; + } + + List targetTypes = targetType.getTupleTypes(); + int sourceTypeSize = source.size(); + int targetTypeSize = targetTypes.size(); + Type targetRestType = targetType.getRestType(); + + if (sourceTypeSize < targetTypeSize) { + return false; + } + if (targetRestType == null && sourceTypeSize > targetTypeSize) { + return false; + } + + for (int i = 0; i < targetTypeSize; i++) { + if (!checkIsLikeType(null, source.getRefValue(i), targetTypes.get(i), unresolvedValues, + allowNumericConversion, null)) { + return false; + } + } + for (int i = targetTypeSize; i < sourceTypeSize; i++) { + if (!checkIsLikeType(null, source.getRefValue(i), targetRestType, unresolvedValues, + allowNumericConversion, null)) { + return false; + } + } + return true; + } + + private static boolean checkIsLikeArrayType(Object sourceValue, BArrayType targetType, + List unresolvedValues, boolean allowNumericConversion) { + if (!(sourceValue instanceof ArrayValue)) { + return false; + } + + ArrayValue source = (ArrayValue) sourceValue; + Type targetTypeElementType = targetType.getElementType(); + if (source.getType().getTag() == TypeTags.ARRAY_TAG) { + Type sourceElementType = ((BArrayType) source.getType()).getElementType(); + if (isValueType(sourceElementType)) { + + if (TypeChecker.checkIsType(sourceElementType, targetTypeElementType, new ArrayList<>())) { + return true; + } + + if (allowNumericConversion && TypeChecker.isNumericType(sourceElementType)) { + if (TypeChecker.isNumericType(targetTypeElementType)) { + return true; + } + + if (targetTypeElementType.getTag() != TypeTags.UNION_TAG) { + return false; + } + + List targetNumericTypes = new ArrayList<>(); + for (Type memType : ((BUnionType) targetTypeElementType).getMemberTypes()) { + if (TypeChecker.isNumericType(memType) && !targetNumericTypes.contains(memType)) { + targetNumericTypes.add(memType); + } + } + return targetNumericTypes.size() == 1; + } + + if (targetTypeElementType.getTag() == TypeTags.FLOAT_TAG || + targetTypeElementType.getTag() == TypeTags.DECIMAL_TAG) { + return false; + } + } + } + + int sourceSize = source.size(); + if ((targetType.getState() != ArrayType.ArrayState.OPEN) && (sourceSize != targetType.getSize())) { + return false; + } + for (int i = 0; i < sourceSize; i++) { + if (!checkIsLikeType(null, source.get(i), targetTypeElementType, unresolvedValues, + allowNumericConversion, null)) { + return false; + } + } + return true; + } + + private static boolean checkIsLikeMapType(Object sourceValue, BMapType targetType, + List unresolvedValues, boolean allowNumericConversion) { + if (!(sourceValue instanceof MapValueImpl)) { + return false; + } + + for (Object mapEntry : ((MapValueImpl) sourceValue).values()) { + if (!checkIsLikeType(null, mapEntry, targetType.getConstrainedType(), unresolvedValues, + allowNumericConversion, null)) { + return false; + } + } + return true; + } + + private static boolean checkIsLikeStreamType(Object sourceValue, BStreamType targetType) { + if (!(sourceValue instanceof StreamValue)) { + return false; + } + + BStreamType streamType = (BStreamType) ((StreamValue) sourceValue).getType(); + + return streamType.getConstrainedType() == targetType.getConstrainedType(); + } + + private static boolean checkIsLikeJSONType(Object sourceValue, Type sourceType, BJsonType targetType, + List unresolvedValues, boolean allowNumericConversion) { + Type referredSourceType = getImpliedType(sourceType); + switch (referredSourceType.getTag()) { + case TypeTags.ARRAY_TAG: + ArrayValue source = (ArrayValue) sourceValue; + Type elementType = ((BArrayType) referredSourceType).getElementType(); + if (TypeChecker.checkIsType(elementType, targetType, new ArrayList<>())) { + return true; + } + + Object[] arrayValues = source.getValues(); + for (int i = 0; i < source.size(); i++) { + if (!checkIsLikeType(null, arrayValues[i], targetType, unresolvedValues, + allowNumericConversion, null)) { + return false; + } + } + return true; + case TypeTags.TUPLE_TAG: + for (Object obj : ((TupleValueImpl) sourceValue).getValues()) { + if (!checkIsLikeType(null, obj, targetType, unresolvedValues, allowNumericConversion, + null)) { + return false; + } + } + return true; + case TypeTags.MAP_TAG: + return checkIsMappingLikeJsonType((MapValueImpl) sourceValue, targetType, unresolvedValues, + allowNumericConversion); + case TypeTags.RECORD_TYPE_TAG: + TypeValuePair typeValuePair = new TypeValuePair(sourceValue, targetType); + if (unresolvedValues.contains(typeValuePair)) { + return true; + } + unresolvedValues.add(typeValuePair); + return checkIsMappingLikeJsonType((MapValueImpl) sourceValue, targetType, unresolvedValues, + allowNumericConversion); + default: + return false; + } + } + + private static boolean checkIsMappingLikeJsonType(MapValueImpl sourceValue, BJsonType targetType, + List unresolvedValues, + boolean allowNumericConversion) { + for (Object value : sourceValue.values()) { + if (!checkIsLikeType(null, value, targetType, unresolvedValues, allowNumericConversion, + null)) { + return false; + } + } + return true; + } + + private static boolean checkIsLikeRecordType(Object sourceValue, BRecordType targetType, + List unresolvedValues, boolean allowNumericConversion, + String varName, List errors) { + if (!(sourceValue instanceof MapValueImpl)) { + return false; + } + + TypeValuePair typeValuePair = new TypeValuePair(sourceValue, targetType); + if (unresolvedValues.contains(typeValuePair)) { + return true; + } + unresolvedValues.add(typeValuePair); + + Map targetFieldTypes = new HashMap<>(); + Type restFieldType = targetType.restFieldType; + boolean returnVal = true; + + for (Field field : targetType.getFields().values()) { + targetFieldTypes.put(field.getFieldName(), field.getFieldType()); + } + + for (Map.Entry targetTypeEntry : targetFieldTypes.entrySet()) { + String fieldName = targetTypeEntry.getKey().toString(); + String fieldNameLong = TypeConverter.getLongFieldName(varName, fieldName); + Field targetField = targetType.getFields().get(fieldName); + + if (!(((MapValueImpl) sourceValue).containsKey(StringUtils.fromString(fieldName))) && + !SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL)) { + addErrorMessage((errors == null) ? 0 : errors.size(), "missing required field '" + + fieldNameLong + "' of type '" + targetField.getFieldType().toString() + + "' in record '" + targetType + "'", + errors); + if ((errors == null) || (errors.size() >= MAX_TYPECAST_ERROR_COUNT + 1)) { + return false; + } + returnVal = false; + } + } + + for (Object object : ((MapValueImpl) sourceValue).entrySet()) { + Map.Entry valueEntry = (Map.Entry) object; + String fieldName = valueEntry.getKey().toString(); + String fieldNameLong = TypeConverter.getLongFieldName(varName, fieldName); + int initialErrorCount = (errors == null) ? 0 : errors.size(); + + if (targetFieldTypes.containsKey(fieldName)) { + if (!checkIsLikeType(errors, (valueEntry.getValue()), targetFieldTypes.get(fieldName), + unresolvedValues, allowNumericConversion, fieldNameLong)) { + addErrorMessage(initialErrorCount, "field '" + fieldNameLong + "' in record '" + targetType + + "' should be of type '" + targetFieldTypes.get(fieldName) + "', found '" + + TypeConverter.getShortSourceValue(valueEntry.getValue()) + "'", errors); + returnVal = false; + } + } else { + if (!targetType.sealed) { + if (!checkIsLikeType(errors, (valueEntry.getValue()), restFieldType, unresolvedValues, + allowNumericConversion, fieldNameLong)) { + addErrorMessage(initialErrorCount, "value of field '" + valueEntry.getKey() + + "' adding to the record '" + targetType + "' should be of type '" + restFieldType + + "', found '" + TypeConverter.getShortSourceValue(valueEntry.getValue()) + "'", errors); + returnVal = false; + } + } else { + addErrorMessage(initialErrorCount, "field '" + fieldNameLong + + "' cannot be added to the closed record '" + targetType + "'", errors); + returnVal = false; + } + } + if ((!returnVal) && ((errors == null) || (errors.size() >= MAX_TYPECAST_ERROR_COUNT + 1))) { + return false; + } + } + return returnVal; + } + + private static void addErrorMessage(int initialErrorCount, String errorMessage, List errors) { + if ((errors != null) && (errors.size() <= MAX_TYPECAST_ERROR_COUNT) && + ((errors.size() - initialErrorCount) == 0)) { + errors.add(errorMessage); + } + } + + private static boolean checkIsLikeTableType(Object sourceValue, BTableType targetType, + List unresolvedValues, boolean allowNumericConversion) { + if (!(sourceValue instanceof TableValueImpl)) { + return false; + } + TableValueImpl tableValue = (TableValueImpl) sourceValue; + BTableType sourceType = (BTableType) getImpliedType(tableValue.getType()); + if (targetType.getKeyType() != null && sourceType.getFieldNames().length == 0) { + return false; + } + + if (sourceType.getKeyType() != null && + !TypeChecker.checkIsType(tableValue.getKeyType(), targetType.getKeyType())) { + return false; + } + + TypeValuePair typeValuePair = new TypeValuePair(sourceValue, targetType); + if (unresolvedValues.contains(typeValuePair)) { + return true; + } + + Object[] objects = tableValue.values().toArray(); + for (Object object : objects) { + if (!TypeChecker.checkIsLikeType(object, targetType.getConstrainedType(), allowNumericConversion)) { + return false; + } + } + return true; + } + + private static boolean checkFiniteTypeAssignable(Object sourceValue, Type sourceType, BFiniteType targetType, + List unresolvedValues, + boolean allowNumericConversion) { + if (targetType.valueSpace.size() == 1) { + Type valueType = getImpliedType(TypeChecker.getType(targetType.valueSpace.iterator().next())); + if (!isSimpleBasicType(valueType) && valueType.getTag() != TypeTags.NULL_TAG) { + return checkIsLikeOnValue(null, sourceValue, sourceType, valueType, unresolvedValues, + allowNumericConversion, null); + } + } + + for (Object valueSpaceItem : targetType.valueSpace) { + // TODO: 8/13/19 Maryam fix for conversion + if (isFiniteTypeValue(sourceValue, sourceType, valueSpaceItem, allowNumericConversion)) { + return true; + } + } + return false; + } + + protected static boolean isFiniteTypeValue(Object sourceValue, Type sourceType, Object valueSpaceItem, + boolean allowNumericConversion) { + Type valueSpaceItemType = TypeChecker.getType(valueSpaceItem); + int sourceTypeTag = getImpliedType(sourceType).getTag(); + int valueSpaceItemTypeTag = getImpliedType(valueSpaceItemType).getTag(); + if (valueSpaceItemTypeTag > TypeTags.DECIMAL_TAG) { + return valueSpaceItemTypeTag == sourceTypeTag && + (valueSpaceItem == sourceValue || valueSpaceItem.equals(sourceValue)); + } + + switch (sourceTypeTag) { + case TypeTags.BYTE_TAG: + case TypeTags.INT_TAG: + switch (valueSpaceItemTypeTag) { + case TypeTags.BYTE_TAG: + case TypeTags.INT_TAG: + return ((Number) sourceValue).longValue() == ((Number) valueSpaceItem).longValue(); + case TypeTags.FLOAT_TAG: + return ((Number) sourceValue).longValue() == ((Number) valueSpaceItem).longValue() && + allowNumericConversion; + case TypeTags.DECIMAL_TAG: + return ((Number) sourceValue).longValue() == ((DecimalValue) valueSpaceItem).intValue() && + allowNumericConversion; + } + case TypeTags.FLOAT_TAG: + switch (valueSpaceItemTypeTag) { + case TypeTags.BYTE_TAG: + case TypeTags.INT_TAG: + return ((Number) sourceValue).doubleValue() == ((Number) valueSpaceItem).doubleValue() + && allowNumericConversion; + case TypeTags.FLOAT_TAG: + return (((Number) sourceValue).doubleValue() == ((Number) valueSpaceItem).doubleValue() || + (Double.isNaN((Double) sourceValue) && Double.isNaN((Double) valueSpaceItem))); + case TypeTags.DECIMAL_TAG: + return ((Number) sourceValue).doubleValue() == ((DecimalValue) valueSpaceItem).floatValue() + && allowNumericConversion; + } + case TypeTags.DECIMAL_TAG: + switch (valueSpaceItemTypeTag) { + case TypeTags.BYTE_TAG: + case TypeTags.INT_TAG: + return TypeChecker.checkDecimalEqual((DecimalValue) sourceValue, + DecimalValue.valueOf(((Number) valueSpaceItem).longValue())) && allowNumericConversion; + case TypeTags.FLOAT_TAG: + return TypeChecker.checkDecimalEqual((DecimalValue) sourceValue, + DecimalValue.valueOf(((Number) valueSpaceItem).doubleValue())) && + allowNumericConversion; + case TypeTags.DECIMAL_TAG: + return TypeChecker.checkDecimalEqual((DecimalValue) sourceValue, (DecimalValue) valueSpaceItem); + } + default: + if (sourceTypeTag != valueSpaceItemTypeTag) { + return false; + } + return valueSpaceItem.equals(sourceValue); + } + } + + private static boolean checkIsLikeErrorType(Object sourceValue, BErrorType targetType, + List unresolvedValues, boolean allowNumericConversion) { + Type sourceTypeReferredType = getImpliedType(TypeChecker.getType(sourceValue)); + if (sourceValue == null || sourceTypeReferredType.getTag() != TypeTags.ERROR_TAG) { + return false; + } + if (!checkIsLikeType(null, ((ErrorValue) sourceValue).getDetails(), targetType.detailType, + unresolvedValues, allowNumericConversion, null)) { + return false; + } + if (targetType.typeIdSet == null) { + return true; + } + BTypeIdSet sourceIdSet = ((BErrorType) sourceTypeReferredType).typeIdSet; + if (sourceIdSet == null) { + return false; + } + return sourceIdSet.containsAll(targetType.typeIdSet); + } + + static boolean isSimpleBasicType(Type type) { + return getImpliedType(type).getTag() < TypeTags.NULL_TAG; + } + + static boolean checkTypeDescType(Type sourceType, BTypedescType targetType, + List unresolvedTypes) { + if (sourceType.getTag() != TypeTags.TYPEDESC_TAG) { + return false; + } + + BTypedescType sourceTypedesc = (BTypedescType) sourceType; + return TypeChecker.checkIsType(sourceTypedesc.getConstraint(), targetType.getConstraint(), unresolvedTypes); + } + + static boolean isXMLValueRefEqual(XmlValue lhsValue, XmlValue rhsValue) { + if (lhsValue.getNodeType() == XmlNodeType.SEQUENCE && lhsValue.isSingleton()) { + return ((XmlSequence) lhsValue).getChildrenList().get(0) == rhsValue; + } + if (rhsValue.getNodeType() == XmlNodeType.SEQUENCE && rhsValue.isSingleton()) { + return ((XmlSequence) rhsValue).getChildrenList().get(0) == lhsValue; + } + if (lhsValue.getNodeType() != rhsValue.getNodeType()) { + return false; + } + if (lhsValue.getNodeType() == XmlNodeType.SEQUENCE && rhsValue.getNodeType() == XmlNodeType.SEQUENCE) { + return isXMLSequenceRefEqual((XmlSequence) lhsValue, (XmlSequence) rhsValue); + } + if (lhsValue.getNodeType() == XmlNodeType.TEXT && rhsValue.getNodeType() == XmlNodeType.TEXT) { + return TypeChecker.isEqual(lhsValue, rhsValue); + } + return false; + } + + private static boolean isXMLSequenceRefEqual(XmlSequence lhsValue, XmlSequence rhsValue) { + Iterator lhsIter = lhsValue.getChildrenList().iterator(); + Iterator rhsIter = rhsValue.getChildrenList().iterator(); + while (lhsIter.hasNext() && rhsIter.hasNext()) { + BXml l = lhsIter.next(); + BXml r = rhsIter.next(); + if (!(l == r || isXMLValueRefEqual((XmlValue) l, (XmlValue) r))) { + return false; + } + } + // lhs hasNext = false & rhs hasNext = false -> empty sequences, hence ref equal + // lhs hasNext = true & rhs hasNext = true would never reach here + // only one hasNext method returns true means sequences are of different sizes, hence not ref equal + return lhsIter.hasNext() == rhsIter.hasNext(); + } + + static boolean checkIsRecursiveType(Type sourceType, Type targetType, List unresolvedTypes) { + switch (targetType.getTag()) { + case TypeTags.MAP_TAG: + return checkIsMapType(sourceType, (BMapType) targetType, unresolvedTypes); + case TypeTags.STREAM_TAG: + return checkIsStreamType(sourceType, (BStreamType) targetType, unresolvedTypes); + case TypeTags.TABLE_TAG: + return checkIsTableType(sourceType, (BTableType) targetType, unresolvedTypes); + case TypeTags.JSON_TAG: + return checkIsJSONType(sourceType, unresolvedTypes); + case TypeTags.RECORD_TYPE_TAG: + return checkIsRecordType(sourceType, (BRecordType) targetType, unresolvedTypes); + case TypeTags.FUNCTION_POINTER_TAG: + return checkIsFunctionType(sourceType, (BFunctionType) targetType); + case TypeTags.ARRAY_TAG: + return checkIsArrayType(sourceType, (BArrayType) targetType, unresolvedTypes); + case TypeTags.TUPLE_TAG: + return checkIsTupleType(sourceType, (BTupleType) targetType, unresolvedTypes); + case TypeTags.UNION_TAG: + return checkIsUnionType(sourceType, (BUnionType) targetType, unresolvedTypes); + case TypeTags.OBJECT_TYPE_TAG: + return checkObjectEquivalency(sourceType, (BObjectType) targetType, + unresolvedTypes); + case TypeTags.FINITE_TYPE_TAG: + return checkIsFiniteType(sourceType, (BFiniteType) targetType); + case TypeTags.FUTURE_TAG: + return checkIsFutureType(sourceType, (BFutureType) targetType, unresolvedTypes); + case TypeTags.ERROR_TAG: + return checkIsErrorType(sourceType, (BErrorType) targetType, unresolvedTypes); + case TypeTags.TYPEDESC_TAG: + return checkTypeDescType(sourceType, (BTypedescType) targetType, unresolvedTypes); + case TypeTags.XML_TAG: + return checkIsXMLType(sourceType, targetType, unresolvedTypes); + default: + // other non-recursive types shouldn't reach here + return false; + } + } + + static boolean checkIsRecursiveTypeOnValue(Object sourceVal, Type sourceType, Type targetType, + int sourceTypeTag, int targetTypeTag, + List unresolvedTypes) { + switch (targetTypeTag) { + case TypeTags.ANYDATA_TAG: + if (sourceTypeTag == TypeTags.OBJECT_TYPE_TAG) { + return false; + } + return checkRecordBelongsToAnydataType((MapValue) sourceVal, (BRecordType) sourceType, unresolvedTypes); + case TypeTags.MAP_TAG: + return checkIsMapType(sourceVal, sourceType, (BMapType) targetType, unresolvedTypes); + case TypeTags.JSON_TAG: + return checkIsMapType(sourceVal, sourceType, + new BMapType(targetType.isReadOnly() ? TYPE_READONLY_JSON : + TYPE_JSON), unresolvedTypes); + case TypeTags.RECORD_TYPE_TAG: + return checkIsRecordType(sourceVal, sourceType, (BRecordType) targetType, unresolvedTypes); + case TypeTags.UNION_TAG: + for (Type type : ((BUnionType) targetType).getMemberTypes()) { + if (TypeChecker.checkIsType(sourceVal, sourceType, type, unresolvedTypes)) { + return true; + } + } + return false; + case TypeTags.OBJECT_TYPE_TAG: + return checkObjectEquivalency(sourceVal, sourceType, (BObjectType) targetType, + unresolvedTypes); + default: + return false; + } + } + + private static boolean checkIsUnionType(Type sourceType, BUnionType targetType, + List unresolvedTypes) { + // If we encounter two types that we are still resolving, then skip it. + // This is done to avoid recursive checking of the same type. + sourceType = getImpliedType(sourceType); + TypeChecker.TypePair pair = new TypeChecker.TypePair(sourceType, targetType); + if (unresolvedTypes.contains(pair)) { + return true; + } + unresolvedTypes.add(pair); + + switch (sourceType.getTag()) { + case TypeTags.UNION_TAG: + case TypeTags.JSON_TAG: + case TypeTags.ANYDATA_TAG: + return isUnionTypeMatch((BUnionType) sourceType, targetType, unresolvedTypes); + case TypeTags.FINITE_TYPE_TAG: + return isFiniteTypeMatch((BFiniteType) sourceType, targetType); + default: + for (Type type : targetType.getMemberTypes()) { + if (TypeChecker.checkIsType(sourceType, type, unresolvedTypes)) { + return true; + } + } + return false; + + } + } + + private static boolean checkIsMapType(Type sourceType, BMapType targetType, + List unresolvedTypes) { + Type targetConstrainedType = targetType.getConstrainedType(); + sourceType = getImpliedType(sourceType); + switch (sourceType.getTag()) { + case TypeTags.MAP_TAG: + return checkConstraints(((BMapType) sourceType).getConstrainedType(), targetConstrainedType, + unresolvedTypes); + case TypeTags.RECORD_TYPE_TAG: + BRecordType recType = (BRecordType) sourceType; + BUnionType wideTypeUnion = new BUnionType(getWideTypeComponents(recType)); + return checkConstraints(wideTypeUnion, targetConstrainedType, unresolvedTypes); + default: + return false; + } + } + + private static boolean checkIsMapType(Object sourceVal, Type sourceType, BMapType targetType, + List unresolvedTypes) { + Type targetConstrainedType = targetType.getConstrainedType(); + sourceType = getImpliedType(sourceType); + switch (sourceType.getTag()) { + case TypeTags.MAP_TAG: + return checkConstraints(((BMapType) sourceType).getConstrainedType(), targetConstrainedType, + unresolvedTypes); + case TypeTags.RECORD_TYPE_TAG: + return checkIsMapType((MapValue) sourceVal, (BRecordType) sourceType, unresolvedTypes, + targetConstrainedType); + default: + return false; + } + } + + private static boolean checkIsMapType(MapValue sourceVal, BRecordType sourceType, + List unresolvedTypes, + Type targetConstrainedType) { + for (Field field : sourceType.getFields().values()) { + if (!SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY)) { + if (!TypeChecker.checkIsType(field.getFieldType(), targetConstrainedType, unresolvedTypes)) { + return false; + } + continue; + } + + BString name = StringUtils.fromString(field.getFieldName()); + + if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL) && !sourceVal.containsKey(name)) { + continue; + } + + if (!TypeChecker.checkIsLikeType(sourceVal.get(name), targetConstrainedType)) { + return false; + } + } + + if (sourceType.sealed) { + return true; + } + + return TypeChecker.checkIsType(sourceType.restFieldType, targetConstrainedType, unresolvedTypes); + } + + private static boolean checkIsXMLType(Type sourceType, Type targetType, + List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + int sourceTag = sourceType.getTag(); + if (sourceTag == TypeTags.FINITE_TYPE_TAG) { + return isFiniteTypeMatch((BFiniteType) sourceType, targetType); + } + + BXmlType target = ((BXmlType) targetType); + if (sourceTag == TypeTags.XML_TAG) { + Type targetConstraint = getRecursiveTargetConstraintType(target); + BXmlType source = (BXmlType) sourceType; + if (source.constraint.getTag() == TypeTags.NEVER_TAG) { + if (targetConstraint.getTag() == TypeTags.UNION_TAG) { + return checkIsUnionType(sourceType, (BUnionType) targetConstraint, unresolvedTypes); + } + return targetConstraint.getTag() == TypeTags.XML_TEXT_TAG || + targetConstraint.getTag() == TypeTags.NEVER_TAG; + } + return TypeChecker.checkIsType(source.constraint, targetConstraint, unresolvedTypes); + } + if (TypeTags.isXMLTypeTag(sourceTag)) { + return TypeChecker.checkIsType(sourceType, target.constraint, unresolvedTypes); + } + return false; + } + + private static Type getRecursiveTargetConstraintType(BXmlType target) { + Type targetConstraint = getImpliedType(target.constraint); + // TODO: Revisit and check why xml>> on chained iteration + while (targetConstraint.getTag() == TypeTags.XML_TAG) { + target = (BXmlType) targetConstraint; + targetConstraint = getImpliedType(target.constraint); + } + return targetConstraint; + } + + private static List getWideTypeComponents(BRecordType recType) { + List types = new ArrayList<>(); + for (Field f : recType.getFields().values()) { + types.add(f.getFieldType()); + } + if (!recType.sealed) { + types.add(recType.restFieldType); + } + return types; + } + + private static boolean checkIsStreamType(Type sourceType, BStreamType targetType, + List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + if (sourceType.getTag() != TypeTags.STREAM_TAG) { + return false; + } + return checkConstraints(((BStreamType) sourceType).getConstrainedType(), targetType.getConstrainedType(), + unresolvedTypes) + && checkConstraints(((BStreamType) sourceType).getCompletionType(), targetType.getCompletionType(), + unresolvedTypes); + } + + private static boolean checkIsTableType(Type sourceType, BTableType targetType, + List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + if (sourceType.getTag() != TypeTags.TABLE_TAG) { + return false; + } + + BTableType srcTableType = (BTableType) sourceType; + + if (!checkConstraints(srcTableType.getConstrainedType(), targetType.getConstrainedType(), + unresolvedTypes)) { + return false; + } + + if (targetType.getKeyType() == null && targetType.getFieldNames().length == 0) { + return true; + } + + if (targetType.getKeyType() != null) { + if (srcTableType.getKeyType() != null && + (checkConstraints(srcTableType.getKeyType(), targetType.getKeyType(), unresolvedTypes))) { + return true; + } + + if (srcTableType.getFieldNames().length == 0) { + return false; + } + + List fieldTypes = new ArrayList<>(); + Arrays.stream(srcTableType.getFieldNames()).forEach(field -> fieldTypes + .add(Objects.requireNonNull(getTableConstraintField(srcTableType.getConstrainedType(), field)) + .getFieldType())); + + if (fieldTypes.size() == 1) { + return checkConstraints(fieldTypes.get(0), targetType.getKeyType(), unresolvedTypes); + } + + BTupleType tupleType = new BTupleType(fieldTypes); + return checkConstraints(tupleType, targetType.getKeyType(), unresolvedTypes); + } + + return Arrays.equals(srcTableType.getFieldNames(), targetType.getFieldNames()); + } + + static BField getTableConstraintField(Type constraintType, String fieldName) { + switch (constraintType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: + Map fieldList = ((BRecordType) constraintType).getFields(); + return (BField) fieldList.get(fieldName); + case TypeTags.INTERSECTION_TAG: + Type effectiveType = ((BIntersectionType) constraintType).getEffectiveType(); + return getTableConstraintField(effectiveType, fieldName); + case TypeTags.TYPE_REFERENCED_TYPE_TAG: + Type referredType = ((BTypeReferenceType) constraintType).getReferredType(); + return getTableConstraintField(referredType, fieldName); + case TypeTags.UNION_TAG: + BUnionType unionType = (BUnionType) constraintType; + List memTypes = unionType.getMemberTypes(); + List fields = memTypes.stream().map(type -> getTableConstraintField(type, fieldName)) + .filter(Objects::nonNull).collect(Collectors.toList()); + + if (fields.size() != memTypes.size()) { + return null; + } + + if (fields.stream().allMatch( + field -> TypeChecker.isSameType(field.getFieldType(), fields.get(0).getFieldType()))) { + return fields.get(0); + } + return null; + default: + return null; + } + } + + private static boolean checkIsJSONType(Type sourceType, List unresolvedTypes) { + BJsonType jsonType = (BJsonType) TYPE_JSON; + sourceType = getImpliedType(sourceType); + // If we encounter two types that we are still resolving, then skip it. + // This is done to avoid recursive checking of the same type. + TypeChecker.TypePair pair = new TypeChecker.TypePair(sourceType, jsonType); + if (unresolvedTypes.contains(pair)) { + return true; + } + unresolvedTypes.add(pair); + + switch (sourceType.getTag()) { + case TypeTags.STRING_TAG: + case TypeTags.CHAR_STRING_TAG: + case TypeTags.INT_TAG: + case TypeTags.SIGNED32_INT_TAG: + case TypeTags.SIGNED16_INT_TAG: + case TypeTags.SIGNED8_INT_TAG: + case TypeTags.UNSIGNED32_INT_TAG: + case TypeTags.UNSIGNED16_INT_TAG: + case TypeTags.UNSIGNED8_INT_TAG: + case TypeTags.BYTE_TAG: + case TypeTags.FLOAT_TAG: + case TypeTags.DECIMAL_TAG: + case TypeTags.BOOLEAN_TAG: + case TypeTags.NULL_TAG: + case TypeTags.JSON_TAG: + return true; + case TypeTags.ARRAY_TAG: + // Element type of the array should be 'is type' JSON + return TypeChecker.checkIsType(((BArrayType) sourceType).getElementType(), jsonType, unresolvedTypes); + case TypeTags.FINITE_TYPE_TAG: + return isFiniteTypeMatch((BFiniteType) sourceType, jsonType); + case TypeTags.MAP_TAG: + return TypeChecker.checkIsType(((BMapType) sourceType).getConstrainedType(), jsonType, unresolvedTypes); + case TypeTags.RECORD_TYPE_TAG: + BRecordType recordType = (BRecordType) sourceType; + for (Field field : recordType.getFields().values()) { + if (!checkIsJSONType(field.getFieldType(), unresolvedTypes)) { + return false; + } + } + + if (!recordType.sealed) { + return checkIsJSONType(recordType.restFieldType, unresolvedTypes); + } + return true; + case TypeTags.TUPLE_TAG: + BTupleType sourceTupleType = (BTupleType) sourceType; + for (Type memberType : sourceTupleType.getTupleTypes()) { + if (!checkIsJSONType(memberType, unresolvedTypes)) { + return false; + } + } + Type tupleRestType = sourceTupleType.getRestType(); + if (tupleRestType != null) { + return checkIsJSONType(tupleRestType, unresolvedTypes); + } + return true; + case TypeTags.UNION_TAG: + for (Type memberType : ((BUnionType) sourceType).getMemberTypes()) { + if (!checkIsJSONType(memberType, unresolvedTypes)) { + return false; + } + } + return true; + default: + return false; + } + } + + private static boolean checkIsRecordType(Type sourceType, BRecordType targetType, + List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + switch (sourceType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: + return checkIsRecordType((BRecordType) sourceType, targetType, unresolvedTypes); + case TypeTags.MAP_TAG: + return checkIsRecordType((BMapType) sourceType, targetType, unresolvedTypes); + default: + return false; + } + } + + private static boolean checkIsRecordType(BRecordType sourceRecordType, BRecordType targetType, + List unresolvedTypes) { + // If we encounter two types that we are still resolving, then skip it. + // This is done to avoid recursive checking of the same type. + TypeChecker.TypePair pair = new TypeChecker.TypePair(sourceRecordType, targetType); + if (unresolvedTypes.contains(pair)) { + return true; + } + unresolvedTypes.add(pair); + + // Unsealed records are not equivalent to sealed records, unless their rest field type is 'never'. But + // vice-versa is allowed. + if (targetType.sealed && !sourceRecordType.sealed && (sourceRecordType.restFieldType == null || + getImpliedType(sourceRecordType.restFieldType).getTag() != TypeTags.NEVER_TAG)) { + return false; + } + + // If both are sealed check the rest field type + if (!sourceRecordType.sealed && !targetType.sealed && + !TypeChecker.checkIsType(sourceRecordType.restFieldType, targetType.restFieldType, unresolvedTypes)) { + return false; + } + + Map sourceFields = sourceRecordType.getFields(); + Set targetFieldNames = targetType.getFields().keySet(); + + for (Map.Entry targetFieldEntry : targetType.getFields().entrySet()) { + Field targetField = targetFieldEntry.getValue(); + Field sourceField = sourceFields.get(targetFieldEntry.getKey()); + + if (sourceField == null) { + if (!SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL)) { + return false; + } + + if (!sourceRecordType.sealed && + !TypeChecker.checkIsType(sourceRecordType.restFieldType, targetField.getFieldType(), + unresolvedTypes)) { + return false; + } + + continue; + } + + if (hasIncompatibleReadOnlyFlags(targetField, sourceField)) { + return false; + } + + // If the target field is required, the source field should be required as well. + if (!SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL) + && SymbolFlags.isFlagOn(sourceField.getFlags(), SymbolFlags.OPTIONAL)) { + return false; + } + + if (!TypeChecker.checkIsType(sourceField.getFieldType(), targetField.getFieldType(), unresolvedTypes)) { + return false; + } + } + + // If there are fields remaining in the source record, first check if it's a closed record. Closed records + // should only have the fields specified by its type. + if (targetType.sealed) { + return targetFieldNames.containsAll(sourceFields.keySet()); + } + + // If it's an open record, check if they are compatible with the rest field of the target type. + for (Map.Entry sourceFieldEntry : sourceFields.entrySet()) { + if (targetFieldNames.contains(sourceFieldEntry.getKey())) { + continue; + } + + if (!TypeChecker.checkIsType(sourceFieldEntry.getValue().getFieldType(), targetType.restFieldType, + unresolvedTypes)) { + return false; + } + } + return true; + } + + private static boolean checkIsRecordType(BMapType sourceType, BRecordType targetType, + List unresolvedTypes) { + // If we encounter two types that we are still resolving, then skip it. + // This is done to avoid recursive checking of the same type. + TypeChecker.TypePair pair = new TypeChecker.TypePair(sourceType, targetType); + if (unresolvedTypes.contains(pair)) { + return true; + } + unresolvedTypes.add(pair); + + if (targetType.sealed) { + return false; + } + + Type constraintType = sourceType.getConstrainedType(); + + for (Field field : targetType.getFields().values()) { + var flags = field.getFlags(); + if (!SymbolFlags.isFlagOn(flags, SymbolFlags.OPTIONAL)) { + return false; + } + + if (SymbolFlags.isFlagOn(flags, SymbolFlags.READONLY) && !sourceType.isReadOnly()) { + return false; + } + + if (!TypeChecker.checkIsType(constraintType, field.getFieldType(), unresolvedTypes)) { + return false; + } + } + + return TypeChecker.checkIsType(constraintType, targetType.restFieldType, unresolvedTypes); + } + + private static boolean checkRecordBelongsToAnydataType(MapValue sourceVal, BRecordType recordType, + List unresolvedTypes) { + Type targetType = TYPE_ANYDATA; + TypeChecker.TypePair pair = new TypeChecker.TypePair(recordType, targetType); + if (unresolvedTypes.contains(pair)) { + return true; + } + unresolvedTypes.add(pair); + + Map fields = recordType.getFields(); + + for (Map.Entry fieldEntry : fields.entrySet()) { + String fieldName = fieldEntry.getKey(); + Field field = fieldEntry.getValue(); + + if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY)) { + BString fieldNameBString = StringUtils.fromString(fieldName); + + if (SymbolFlags + .isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL) && !sourceVal.containsKey(fieldNameBString)) { + continue; + } + + if (!TypeChecker.checkIsLikeType(sourceVal.get(fieldNameBString), targetType)) { + return false; + } + } else { + if (!TypeChecker.checkIsType(field.getFieldType(), targetType, unresolvedTypes)) { + return false; + } + } + } + + if (recordType.sealed) { + return true; + } + + return TypeChecker.checkIsType(recordType.restFieldType, targetType, unresolvedTypes); + } + + private static boolean checkIsRecordType(Object sourceVal, Type sourceType, BRecordType targetType, + List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + switch (sourceType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: + return checkIsRecordType((MapValue) sourceVal, (BRecordType) sourceType, targetType, unresolvedTypes); + case TypeTags.MAP_TAG: + return checkIsRecordType((BMapType) sourceType, targetType, unresolvedTypes); + default: + return false; + } + } + + private static boolean checkIsRecordType(MapValue sourceRecordValue, BRecordType sourceRecordType, + BRecordType targetType, List unresolvedTypes) { + TypeChecker.TypePair pair = new TypeChecker.TypePair(sourceRecordType, targetType); + if (unresolvedTypes.contains(pair)) { + return true; + } + unresolvedTypes.add(pair); + + // Unsealed records are not equivalent to sealed records, unless their rest field type is 'never'. But + // vice-versa is allowed. + if (targetType.sealed && !sourceRecordType.sealed && (sourceRecordType.restFieldType == null || + getImpliedType(sourceRecordType.restFieldType).getTag() != TypeTags.NEVER_TAG)) { + return false; + } + + // If both are sealed check the rest field type + if (!sourceRecordType.sealed && !targetType.sealed && + !TypeChecker.checkIsType(sourceRecordType.restFieldType, targetType.restFieldType, unresolvedTypes)) { + return false; + } + + Map sourceFields = sourceRecordType.getFields(); + Set targetFieldNames = targetType.getFields().keySet(); + + for (Map.Entry targetFieldEntry : targetType.getFields().entrySet()) { + String fieldName = targetFieldEntry.getKey(); + Field targetField = targetFieldEntry.getValue(); + Field sourceField = sourceFields.get(fieldName); + + if (getImpliedType(targetField.getFieldType()).getTag() == TypeTags.NEVER_TAG && + containsInvalidNeverField(sourceField, sourceRecordType)) { + return false; + } + + if (sourceField == null) { + if (!SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL)) { + return false; + } + + if (!sourceRecordType.sealed && + !TypeChecker.checkIsType(sourceRecordType.restFieldType, targetField.getFieldType(), + unresolvedTypes)) { + return false; + } + + continue; + } + + if (hasIncompatibleReadOnlyFlags(targetField, sourceField)) { + return false; + } + + boolean optionalTargetField = SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL); + boolean optionalSourceField = SymbolFlags.isFlagOn(sourceField.getFlags(), SymbolFlags.OPTIONAL); + + if (SymbolFlags.isFlagOn(sourceField.getFlags(), SymbolFlags.READONLY)) { + BString fieldNameBString = StringUtils.fromString(fieldName); + + if (optionalSourceField && !sourceRecordValue.containsKey(fieldNameBString)) { + if (!optionalTargetField) { + return false; + } + continue; + } + + if (!TypeChecker.checkIsLikeType(sourceRecordValue.get(fieldNameBString), targetField.getFieldType())) { + return false; + } + } else { + if (!optionalTargetField && optionalSourceField) { + return false; + } + + if (!TypeChecker.checkIsType(sourceField.getFieldType(), targetField.getFieldType(), unresolvedTypes)) { + return false; + } + } + } + + if (targetType.sealed) { + for (String sourceFieldName : sourceFields.keySet()) { + if (targetFieldNames.contains(sourceFieldName)) { + continue; + } + + if (!checkIsNeverTypeOrStructureTypeWithARequiredNeverMember( + sourceFields.get(sourceFieldName).getFieldType())) { + return false; + } + } + return true; + } + + for (Map.Entry targetFieldEntry : sourceFields.entrySet()) { + String fieldName = targetFieldEntry.getKey(); + Field field = targetFieldEntry.getValue(); + if (targetFieldNames.contains(fieldName)) { + continue; + } + + if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY)) { + if (!TypeChecker.checkIsLikeType(sourceRecordValue.get(StringUtils.fromString(fieldName)), + targetType.restFieldType)) { + return false; + } + } else if (!TypeChecker.checkIsType(field.getFieldType(), targetType.restFieldType, unresolvedTypes)) { + return false; + } + } + return true; + } + + private static boolean containsInvalidNeverField(Field sourceField, BRecordType sourceRecordType) { + if (sourceField != null) { + return !containsNeverType(sourceField.getFieldType()); + } + if (sourceRecordType.isSealed()) { + return true; + } + return !containsNeverType(sourceRecordType.getRestFieldType()); + } + + private static boolean containsNeverType(Type fieldType) { + fieldType = getImpliedType(fieldType); + int fieldTag = fieldType.getTag(); + if (fieldTag == TypeTags.NEVER_TAG) { + return true; + } + if (fieldTag == TypeTags.UNION_TAG) { + List memberTypes = ((BUnionType) fieldType).getOriginalMemberTypes(); + for (Type member : memberTypes) { + if (getImpliedType(member).getTag() == TypeTags.NEVER_TAG) { + return true; + } + } + } + return false; + } + + private static boolean checkIsArrayType(BArrayType sourceType, BArrayType targetType, + List unresolvedTypes) { + switch (sourceType.getState()) { + case OPEN: + if (targetType.getState() != ArrayType.ArrayState.OPEN) { + return false; + } + break; + case CLOSED: + if (targetType.getState() == ArrayType.ArrayState.CLOSED && + sourceType.getSize() != targetType.getSize()) { + return false; + } + break; + default: + break; + } + return TypeChecker.checkIsType(sourceType.getElementType(), targetType.getElementType(), unresolvedTypes); + } + + private static boolean checkIsArrayType(BTupleType sourceType, BArrayType targetType, + List unresolvedTypes) { + List tupleTypes = sourceType.getTupleTypes(); + Type sourceRestType = sourceType.getRestType(); + Type targetElementType = targetType.getElementType(); + + if (targetType.getState() == ArrayType.ArrayState.OPEN) { + for (Type sourceElementType : tupleTypes) { + if (!TypeChecker.checkIsType(sourceElementType, targetElementType, unresolvedTypes)) { + return false; + } + } + if (sourceRestType != null) { + return TypeChecker.checkIsType(sourceRestType, targetElementType, unresolvedTypes); + } + return true; + } + if (sourceRestType != null) { + return false; + } + if (tupleTypes.size() != targetType.getSize()) { + return false; + } + for (Type sourceElementType : tupleTypes) { + if (!TypeChecker.checkIsType(sourceElementType, targetElementType, unresolvedTypes)) { + return false; + } + } + return true; + } + + private static boolean checkIsArrayType(Type sourceType, BArrayType targetType, + List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + int sourceTypeTag = sourceType.getTag(); + + if (sourceTypeTag == TypeTags.UNION_TAG) { + for (Type memberType : ((BUnionType) sourceType).getMemberTypes()) { + if (!checkIsArrayType(memberType, targetType, unresolvedTypes)) { + return false; + } + } + return true; + } + + if (sourceTypeTag != TypeTags.ARRAY_TAG && sourceTypeTag != TypeTags.TUPLE_TAG) { + return false; + } + + if (sourceTypeTag == TypeTags.ARRAY_TAG) { + return checkIsArrayType((BArrayType) sourceType, targetType, unresolvedTypes); + } + return checkIsArrayType((BTupleType) sourceType, targetType, unresolvedTypes); + } + + private static boolean checkIsTupleType(BArrayType sourceType, BTupleType targetType, + List unresolvedTypes) { + Type sourceElementType = sourceType.getElementType(); + List targetTypes = targetType.getTupleTypes(); + Type targetRestType = targetType.getRestType(); + + switch (sourceType.getState()) { + case OPEN: + if (targetRestType == null) { + return false; + } + if (targetTypes.isEmpty()) { + return TypeChecker.checkIsType(sourceElementType, targetRestType, unresolvedTypes); + } + return false; + case CLOSED: + if (sourceType.getSize() < targetTypes.size()) { + return false; + } + if (targetTypes.isEmpty()) { + if (targetRestType != null) { + return TypeChecker.checkIsType(sourceElementType, targetRestType, unresolvedTypes); + } + return sourceType.getSize() == 0; + } + + for (Type targetElementType : targetTypes) { + if (!(TypeChecker.checkIsType(sourceElementType, targetElementType, unresolvedTypes))) { + return false; + } + } + if (sourceType.getSize() == targetTypes.size()) { + return true; + } + if (targetRestType != null) { + return TypeChecker.checkIsType(sourceElementType, targetRestType, unresolvedTypes); + } + return false; + default: + return false; + } + } + + private static boolean checkIsTupleType(BTupleType sourceType, BTupleType targetType, + List unresolvedTypes) { + List sourceTypes = sourceType.getTupleTypes(); + Type sourceRestType = sourceType.getRestType(); + List targetTypes = targetType.getTupleTypes(); + Type targetRestType = targetType.getRestType(); + + if (sourceRestType != null && targetRestType == null) { + return false; + } + int sourceTypeSize = sourceTypes.size(); + int targetTypeSize = targetTypes.size(); + + if (sourceRestType == null && targetRestType == null && sourceTypeSize != targetTypeSize) { + return false; + } + + if (sourceTypeSize < targetTypeSize) { + return false; + } + + for (int i = 0; i < targetTypeSize; i++) { + if (!TypeChecker.checkIsType(sourceTypes.get(i), targetTypes.get(i), unresolvedTypes)) { + return false; + } + } + if (sourceTypeSize == targetTypeSize) { + if (sourceRestType != null) { + return TypeChecker.checkIsType(sourceRestType, targetRestType, unresolvedTypes); + } + return true; + } + + for (int i = targetTypeSize; i < sourceTypeSize; i++) { + if (!TypeChecker.checkIsType(sourceTypes.get(i), targetRestType, unresolvedTypes)) { + return false; + } + } + if (sourceRestType != null) { + return TypeChecker.checkIsType(sourceRestType, targetRestType, unresolvedTypes); + } + return true; + } + + private static boolean checkIsTupleType(Type sourceType, BTupleType targetType, + List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + int sourceTypeTag = sourceType.getTag(); + + if (sourceTypeTag == TypeTags.UNION_TAG) { + for (Type memberType : ((BUnionType) sourceType).getMemberTypes()) { + if (!checkIsTupleType(memberType, targetType, unresolvedTypes)) { + return false; + } + } + return true; + } + + if (sourceTypeTag != TypeTags.ARRAY_TAG && sourceTypeTag != TypeTags.TUPLE_TAG) { + return false; + } + + if (sourceTypeTag == TypeTags.ARRAY_TAG) { + return checkIsTupleType((BArrayType) sourceType, targetType, unresolvedTypes); + } + return checkIsTupleType((BTupleType) sourceType, targetType, unresolvedTypes); + } + + private static boolean checkIsFiniteType(Type sourceType, BFiniteType targetType) { + sourceType = getImpliedType(sourceType); + if (sourceType.getTag() != TypeTags.FINITE_TYPE_TAG) { + return false; + } + + BFiniteType sourceFiniteType = (BFiniteType) sourceType; + if (sourceFiniteType.valueSpace.size() != targetType.valueSpace.size()) { + return false; + } + + return targetType.valueSpace.containsAll(sourceFiniteType.valueSpace); + } + + private static boolean checkIsFutureType(Type sourceType, BFutureType targetType, + List unresolvedTypes) { + sourceType = getImpliedType(sourceType); + if (sourceType.getTag() != TypeTags.FUTURE_TAG) { + return false; + } + return checkConstraints(((BFutureType) sourceType).getConstrainedType(), targetType.getConstrainedType(), + unresolvedTypes); + } + + private static boolean checkIsFunctionType(Type sourceType, BFunctionType targetType) { + sourceType = getImpliedType(sourceType); + if (sourceType.getTag() != TypeTags.FUNCTION_POINTER_TAG) { + return false; + } + + BFunctionType source = (BFunctionType) sourceType; + if (hasIncompatibleIsolatedFlags(targetType, source) || + hasIncompatibleTransactionalFlags(targetType, source)) { + return false; + } + + if (SymbolFlags.isFlagOn(targetType.getFlags(), SymbolFlags.ANY_FUNCTION)) { + return true; + } + + if (source.parameters.length != targetType.parameters.length) { + return false; + } + + for (int i = 0; i < source.parameters.length; i++) { + if (!TypeChecker.checkIsType(targetType.parameters[i].type, source.parameters[i].type, new ArrayList<>())) { + return false; + } + } + + return TypeChecker.checkIsType(source.retType, targetType.retType, new ArrayList<>()); + } + + private static boolean hasIncompatibleTransactionalFlags(FunctionType target, FunctionType source) { + return SymbolFlags.isFlagOn(source.getFlags(), SymbolFlags.TRANSACTIONAL) && !SymbolFlags + .isFlagOn(target.getFlags(), SymbolFlags.TRANSACTIONAL); + } + + private static boolean checkConstraints(Type sourceConstraint, Type targetConstraint, + List unresolvedTypes) { + if (sourceConstraint == null) { + sourceConstraint = TYPE_ANY; + } + + if (targetConstraint == null) { + targetConstraint = TYPE_ANY; + } + + return TypeChecker.checkIsType(sourceConstraint, targetConstraint, unresolvedTypes); + } + + private static boolean checkIsErrorType(Type sourceType, BErrorType targetType, + List unresolvedTypes) { + if (sourceType.getTag() != TypeTags.ERROR_TAG) { + return false; + } + // Handle recursive error types. + TypeChecker.TypePair pair = new TypeChecker.TypePair(sourceType, targetType); + if (unresolvedTypes.contains(pair)) { + return true; + } + unresolvedTypes.add(pair); + BErrorType bErrorType = (BErrorType) sourceType; + + if (!TypeChecker.checkIsType(bErrorType.detailType, targetType.detailType, unresolvedTypes)) { + return false; + } + + if (targetType.typeIdSet == null) { + return true; + } + + BTypeIdSet sourceTypeIdSet = bErrorType.typeIdSet; + if (sourceTypeIdSet == null) { + return false; + } + + return sourceTypeIdSet.containsAll(targetType.typeIdSet); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java index 12448f88f17a..dfe2e8541738 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java @@ -18,60 +18,49 @@ package io.ballerina.runtime.internal; import io.ballerina.runtime.api.Module; -import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.flags.SymbolFlags; import io.ballerina.runtime.api.types.ArrayType.ArrayState; import io.ballerina.runtime.api.types.Field; import io.ballerina.runtime.api.types.FunctionType; -import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.MethodType; +import io.ballerina.runtime.api.types.ParameterizedType; import io.ballerina.runtime.api.types.Type; -import io.ballerina.runtime.api.types.UnionType; -import io.ballerina.runtime.api.types.XmlNodeType; -import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.types.semtype.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BRefValue; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BValue; -import io.ballerina.runtime.api.values.BXml; -import io.ballerina.runtime.internal.commons.TypeValuePair; import io.ballerina.runtime.internal.types.BAnnotatableType; import io.ballerina.runtime.internal.types.BArrayType; -import io.ballerina.runtime.internal.types.BErrorType; -import io.ballerina.runtime.internal.types.BField; +import io.ballerina.runtime.internal.types.BBooleanType; +import io.ballerina.runtime.internal.types.BByteType; import io.ballerina.runtime.internal.types.BFiniteType; -import io.ballerina.runtime.internal.types.BFunctionType; -import io.ballerina.runtime.internal.types.BFutureType; +import io.ballerina.runtime.internal.types.BFloatType; +import io.ballerina.runtime.internal.types.BIntegerType; import io.ballerina.runtime.internal.types.BIntersectionType; -import io.ballerina.runtime.internal.types.BJsonType; import io.ballerina.runtime.internal.types.BMapType; -import io.ballerina.runtime.internal.types.BNetworkObjectType; import io.ballerina.runtime.internal.types.BObjectType; -import io.ballerina.runtime.internal.types.BParameterizedType; import io.ballerina.runtime.internal.types.BRecordType; -import io.ballerina.runtime.internal.types.BResourceMethodType; -import io.ballerina.runtime.internal.types.BStreamType; import io.ballerina.runtime.internal.types.BTableType; import io.ballerina.runtime.internal.types.BTupleType; import io.ballerina.runtime.internal.types.BType; -import io.ballerina.runtime.internal.types.BTypeIdSet; import io.ballerina.runtime.internal.types.BTypeReferenceType; -import io.ballerina.runtime.internal.types.BTypedescType; import io.ballerina.runtime.internal.types.BUnionType; import io.ballerina.runtime.internal.types.BXmlType; import io.ballerina.runtime.internal.values.ArrayValue; import io.ballerina.runtime.internal.values.DecimalValue; import io.ballerina.runtime.internal.values.ErrorValue; import io.ballerina.runtime.internal.values.HandleValue; -import io.ballerina.runtime.internal.values.MapValue; import io.ballerina.runtime.internal.values.MapValueImpl; import io.ballerina.runtime.internal.values.RegExpValue; -import io.ballerina.runtime.internal.values.StreamValue; import io.ballerina.runtime.internal.values.TableValueImpl; -import io.ballerina.runtime.internal.values.TupleValueImpl; import io.ballerina.runtime.internal.values.TypedescValue; import io.ballerina.runtime.internal.values.TypedescValueImpl; import io.ballerina.runtime.internal.values.ValuePair; @@ -83,19 +72,11 @@ import io.ballerina.runtime.internal.values.XmlValue; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import java.util.Set; -import static io.ballerina.runtime.api.PredefinedTypes.TYPE_ANY; -import static io.ballerina.runtime.api.PredefinedTypes.TYPE_ANYDATA; import static io.ballerina.runtime.api.PredefinedTypes.TYPE_BOOLEAN; import static io.ballerina.runtime.api.PredefinedTypes.TYPE_BYTE; import static io.ballerina.runtime.api.PredefinedTypes.TYPE_DECIMAL; @@ -107,10 +88,7 @@ import static io.ballerina.runtime.api.PredefinedTypes.TYPE_INT_UNSIGNED_16; import static io.ballerina.runtime.api.PredefinedTypes.TYPE_INT_UNSIGNED_32; import static io.ballerina.runtime.api.PredefinedTypes.TYPE_INT_UNSIGNED_8; -import static io.ballerina.runtime.api.PredefinedTypes.TYPE_JSON; import static io.ballerina.runtime.api.PredefinedTypes.TYPE_NULL; -import static io.ballerina.runtime.api.PredefinedTypes.TYPE_READONLY_JSON; -import static io.ballerina.runtime.api.PredefinedTypes.TYPE_STRING; import static io.ballerina.runtime.api.constants.RuntimeConstants.BALLERINA_BUILTIN_PKG_PREFIX; import static io.ballerina.runtime.api.constants.RuntimeConstants.BBYTE_MAX_VALUE; import static io.ballerina.runtime.api.constants.RuntimeConstants.BBYTE_MIN_VALUE; @@ -124,12 +102,10 @@ import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED16_MAX_VALUE; import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED32_MAX_VALUE; import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED8_MAX_VALUE; +import static io.ballerina.runtime.api.types.semtype.Core.B_TYPE_TOP; +import static io.ballerina.runtime.api.types.semtype.Core.SEMTYPE_TOP; import static io.ballerina.runtime.api.utils.TypeUtils.getImpliedType; -import static io.ballerina.runtime.api.utils.TypeUtils.isValueType; import static io.ballerina.runtime.internal.CloneUtils.getErrorMessage; -import static io.ballerina.runtime.internal.TypeConverter.ERROR_MESSAGE_UNION_END; -import static io.ballerina.runtime.internal.TypeConverter.ERROR_MESSAGE_UNION_SEPARATOR; -import static io.ballerina.runtime.internal.TypeConverter.ERROR_MESSAGE_UNION_START; /** * Responsible for performing runtime type checking. @@ -139,8 +115,8 @@ @SuppressWarnings({"rawtypes"}) public class TypeChecker { - private static final byte MAX_TYPECAST_ERROR_COUNT = 20; private static final String REG_EXP_TYPENAME = "RegExp"; + private static final Context cx = new Context(); public static Object checkCast(Object sourceVal, Type targetType) { @@ -279,7 +255,22 @@ public static boolean anyToJBoolean(Object sourceVal) { * @return true if the value belongs to the given type, false otherwise */ public static boolean checkIsType(Object sourceVal, Type targetType) { - return checkIsType(null, sourceVal, getType(sourceVal), targetType); + SemType targetSemType = Builder.from(targetType); + SemType targetBasicTypeUnion = Core.widenToBasicTypeUnion(targetSemType); + SemType valueBasicType = basicType(sourceVal); + if (!Core.isSubtypeSimple(valueBasicType, targetBasicTypeUnion)) { + return false; + } + if (targetBasicTypeUnion == targetSemType) { + return true; + } + SemType sourceSemType = Builder.from(getType(sourceVal)); + return switch (isSubTypeInner(sourceSemType, targetSemType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> FallbackTypeChecker.checkIsType(null, sourceVal, bTypePart(sourceSemType), + bTypePart(targetSemType)); + }; } /** @@ -292,22 +283,12 @@ public static boolean checkIsType(Object sourceVal, Type targetType) { * @return true if the value belongs to the given type, false otherwise */ public static boolean checkIsType(List errors, Object sourceVal, Type sourceType, Type targetType) { - if (checkIsType(sourceVal, sourceType, targetType, null)) { - return true; - } - - if (getImpliedType(sourceType).getTag() == TypeTags.XML_TAG && !targetType.isReadOnly()) { - XmlValue val = (XmlValue) sourceVal; - if (val.getNodeType() == XmlNodeType.SEQUENCE) { - return checkIsLikeOnValue(errors, sourceVal, sourceType, targetType, new ArrayList<>(), false, null); - } - } - - if (isMutable(sourceVal, sourceType)) { - return false; - } - - return checkIsLikeOnValue(errors, sourceVal, sourceType, targetType, new ArrayList<>(), false, null); + return switch (isSubType(sourceType, targetType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> + FallbackTypeChecker.checkIsType(errors, sourceVal, bTypePart(sourceType), bTypePart(targetType)); + }; } /** @@ -330,7 +311,8 @@ public static boolean checkIsLikeType(Object sourceValue, Type targetType) { * @return true if the value has the same shape as the given type; false otherwise */ public static boolean checkIsLikeType(Object sourceValue, Type targetType, boolean allowNumericConversion) { - return checkIsLikeType(null, sourceValue, targetType, new ArrayList<>(), allowNumericConversion, + return FallbackTypeChecker.checkIsLikeType(null, sourceValue, targetType, new ArrayList<>(), + allowNumericConversion, null); } @@ -348,18 +330,19 @@ public static boolean isSameType(Type sourceType, Type targetType) { public static Type getType(Object value) { if (value == null) { return TYPE_NULL; - } else if (value instanceof Number) { + } else if (value instanceof Number number) { + if (value instanceof Double) { + return BFloatType.singletonType(number.doubleValue()); + } + long numberValue = + number instanceof Byte byteValue ? Byte.toUnsignedLong(byteValue) : number.longValue(); if (value instanceof Long) { - return TYPE_INT; - } else if (value instanceof Double) { - return TYPE_FLOAT; + return BIntegerType.singletonType(numberValue); } else if (value instanceof Integer || value instanceof Byte) { - return TYPE_BYTE; + return BByteType.singletonType(numberValue); } - } else if (value instanceof BString) { - return TYPE_STRING; - } else if (value instanceof Boolean) { - return TYPE_BOOLEAN; + } else if (value instanceof Boolean booleanValue) { + return BBooleanType.singletonType(booleanValue); } else if (value instanceof BObject) { return ((BObject) value).getOriginalType(); } @@ -378,18 +361,6 @@ public static boolean isEqual(Object lhsValue, Object rhsValue) { return isEqual(lhsValue, rhsValue, new HashSet<>()); } - /** - * Check if two decimal values are equal in value. - * - * @param lhsValue The value on the left hand side - * @param rhsValue The value of the right hand side - * @return True if values are equal, else false. - */ - public static boolean checkDecimalEqual(DecimalValue lhsValue, DecimalValue rhsValue) { - return isDecimalRealNumber(lhsValue) && isDecimalRealNumber(rhsValue) && - lhsValue.decimalValue().compareTo(rhsValue.decimalValue()) == 0; - } - /** * Check if two decimal values are exactly equal. * @@ -399,20 +370,10 @@ public static boolean checkDecimalEqual(DecimalValue lhsValue, DecimalValue rhsV */ public static boolean checkDecimalExactEqual(DecimalValue lhsValue, DecimalValue rhsValue) { - return isDecimalRealNumber(lhsValue) && isDecimalRealNumber(rhsValue) + return FallbackTypeChecker.isDecimalRealNumber(lhsValue) && FallbackTypeChecker.isDecimalRealNumber(rhsValue) && lhsValue.decimalValue().equals(rhsValue.decimalValue()); } - /** - * Checks if the given decimal number is a real number. - * - * @param decimalValue The decimal value being checked - * @return True if the decimal value is a real number. - */ - private static boolean isDecimalRealNumber(DecimalValue decimalValue) { - return decimalValue.valueKind == DecimalValueKind.ZERO || decimalValue.valueKind == DecimalValueKind.OTHER; - } - /** * Reference equality check for values. If both the values are simple basic types, returns the same * result as {@link #isEqual(Object, Object, Set)} @@ -459,7 +420,7 @@ public static boolean isReferenceEqual(Object lhsValue, Object rhsValue) { if (!TypeTags.isXMLTypeTag(rhsType.getTag())) { return false; } - return isXMLValueRefEqual((XmlValue) lhsValue, (XmlValue) rhsValue); + return FallbackTypeChecker.isXMLValueRefEqual((XmlValue) lhsValue, (XmlValue) rhsValue); case TypeTags.HANDLE_TAG: if (rhsType.getTag() != TypeTags.HANDLE_TAG) { return false; @@ -476,41 +437,6 @@ public static boolean isReferenceEqual(Object lhsValue, Object rhsValue) { } } - private static boolean isXMLValueRefEqual(XmlValue lhsValue, XmlValue rhsValue) { - if (lhsValue.getNodeType() == XmlNodeType.SEQUENCE && lhsValue.isSingleton()) { - return ((XmlSequence) lhsValue).getChildrenList().get(0) == rhsValue; - } - if (rhsValue.getNodeType() == XmlNodeType.SEQUENCE && rhsValue.isSingleton()) { - return ((XmlSequence) rhsValue).getChildrenList().get(0) == lhsValue; - } - if (lhsValue.getNodeType() != rhsValue.getNodeType()) { - return false; - } - if (lhsValue.getNodeType() == XmlNodeType.SEQUENCE && rhsValue.getNodeType() == XmlNodeType.SEQUENCE) { - return isXMLSequenceRefEqual((XmlSequence) lhsValue, (XmlSequence) rhsValue); - } - if (lhsValue.getNodeType() == XmlNodeType.TEXT && rhsValue.getNodeType() == XmlNodeType.TEXT) { - return isEqual(lhsValue, rhsValue); - } - return false; - } - - private static boolean isXMLSequenceRefEqual(XmlSequence lhsValue, XmlSequence rhsValue) { - Iterator lhsIter = lhsValue.getChildrenList().iterator(); - Iterator rhsIter = rhsValue.getChildrenList().iterator(); - while (lhsIter.hasNext() && rhsIter.hasNext()) { - BXml l = lhsIter.next(); - BXml r = rhsIter.next(); - if (!(l == r || isXMLValueRefEqual((XmlValue) l, (XmlValue) r))) { - return false; - } - } - // lhs hasNext = false & rhs hasNext = false -> empty sequences, hence ref equal - // lhs hasNext = true & rhs hasNext = true would never reach here - // only one hasNext method returns true means sequences are of different sizes, hence not ref equal - return lhsIter.hasNext() == rhsIter.hasNext(); - } - /** * Get the typedesc of a value. * @@ -522,7 +448,7 @@ public static TypedescValue getTypedesc(Object value) { if (type == null) { return null; } - if (isSimpleBasicType(type)) { + if (FallbackTypeChecker.isSimpleBasicType(type)) { return new TypedescValueImpl(new BFiniteType(value.toString(), Set.of(value), 0)); } if (value instanceof BRefValue) { @@ -554,1963 +480,235 @@ public static Object getAnnotValue(TypedescValue typedescValue, BString annotTag * @return flag indicating the equivalence of the two types */ public static boolean checkIsType(Type sourceType, Type targetType) { - return checkIsType(sourceType, targetType, null); + return switch (isSubType(sourceType, targetType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> FallbackTypeChecker.checkIsType(bTypePart(sourceType), bTypePart(targetType), null); + }; } @Deprecated public static boolean checkIsType(Type sourceType, Type targetType, List unresolvedTypes) { - // First check whether both types are the same. - if (sourceType == targetType || (sourceType.getTag() == targetType.getTag() && sourceType.equals(targetType))) { - return true; - } + return switch (isSubType(sourceType, targetType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> + FallbackTypeChecker.checkIsType(bTypePart(sourceType), bTypePart(targetType), unresolvedTypes); + }; + } - if (checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(sourceType)) { - return true; - } + static boolean checkIsType(Object sourceVal, Type sourceType, Type targetType, List unresolvedTypes) { + return switch (isSubType(sourceType, targetType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> FallbackTypeChecker.checkIsType(sourceVal, bTypePart(sourceType), bTypePart(targetType), + unresolvedTypes); + }; + } - if (targetType.isReadOnly() && !sourceType.isReadOnly()) { - return false; - } + /** + * Check if two decimal values are equal in value. + * + * @param lhsValue The value on the left hand side + * @param rhsValue The value of the right hand side + * @return True if values are equal, else false. + */ + public static boolean checkDecimalEqual(DecimalValue lhsValue, DecimalValue rhsValue) { + return FallbackTypeChecker.isDecimalRealNumber(lhsValue) && FallbackTypeChecker.isDecimalRealNumber(rhsValue) && + lhsValue.decimalValue().compareTo(rhsValue.decimalValue()) == 0; + } - int sourceTypeTag = sourceType.getTag(); - int targetTypeTag = targetType.getTag(); + public static boolean isNumericType(Type type) { + type = getImpliedType(type); + return type.getTag() < TypeTags.STRING_TAG || TypeTags.isIntegerTypeTag(type.getTag()); + } - switch (sourceTypeTag) { - case TypeTags.INTERSECTION_TAG: - return checkIsType(((BIntersectionType) sourceType).getEffectiveType(), - targetTypeTag != TypeTags.INTERSECTION_TAG ? targetType : - ((BIntersectionType) targetType).getEffectiveType(), unresolvedTypes); - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return checkIsType(((BTypeReferenceType) sourceType).getReferredType(), - targetTypeTag != TypeTags.TYPE_REFERENCED_TYPE_TAG ? targetType : - ((BTypeReferenceType) targetType).getReferredType(), unresolvedTypes); - case TypeTags.PARAMETERIZED_TYPE_TAG: - if (targetTypeTag != TypeTags.PARAMETERIZED_TYPE_TAG) { - return checkIsType(((BParameterizedType) sourceType).getParamValueType(), targetType, - unresolvedTypes); - } - return checkIsType(((BParameterizedType) sourceType).getParamValueType(), - ((BParameterizedType) targetType).getParamValueType(), unresolvedTypes); - case TypeTags.READONLY_TAG: - return checkIsType(PredefinedTypes.ANY_AND_READONLY_OR_ERROR_TYPE, - targetType, unresolvedTypes); - case TypeTags.UNION_TAG: - return isUnionTypeMatch((BUnionType) sourceType, targetType, unresolvedTypes); - case TypeTags.FINITE_TYPE_TAG: - if ((targetTypeTag == TypeTags.FINITE_TYPE_TAG || targetTypeTag <= TypeTags.NULL_TAG || - targetTypeTag == TypeTags.XML_TEXT_TAG)) { - return isFiniteTypeMatch((BFiniteType) sourceType, targetType); - } - break; - default: - break; - } + static boolean isByteLiteral(long longValue) { + return (longValue >= BBYTE_MIN_VALUE && longValue <= BBYTE_MAX_VALUE); + } - switch (targetTypeTag) { - case TypeTags.BYTE_TAG: - case TypeTags.SIGNED8_INT_TAG: - case TypeTags.FLOAT_TAG: - case TypeTags.DECIMAL_TAG: - case TypeTags.CHAR_STRING_TAG: - case TypeTags.BOOLEAN_TAG: - case TypeTags.NULL_TAG: - return sourceTypeTag == targetTypeTag; - case TypeTags.STRING_TAG: - return TypeTags.isStringTypeTag(sourceTypeTag); - case TypeTags.XML_TEXT_TAG: - if (sourceTypeTag == TypeTags.XML_TAG) { - return ((BXmlType) sourceType).constraint.getTag() == TypeTags.NEVER_TAG; - } - return sourceTypeTag == targetTypeTag; - case TypeTags.INT_TAG: - return sourceTypeTag == TypeTags.INT_TAG || sourceTypeTag == TypeTags.BYTE_TAG || - (sourceTypeTag >= TypeTags.SIGNED8_INT_TAG && sourceTypeTag <= TypeTags.UNSIGNED32_INT_TAG); - case TypeTags.SIGNED16_INT_TAG: - return sourceTypeTag == TypeTags.BYTE_TAG || - (sourceTypeTag >= TypeTags.SIGNED8_INT_TAG && sourceTypeTag <= TypeTags.SIGNED16_INT_TAG); - case TypeTags.SIGNED32_INT_TAG: - return sourceTypeTag == TypeTags.BYTE_TAG || - (sourceTypeTag >= TypeTags.SIGNED8_INT_TAG && sourceTypeTag <= TypeTags.SIGNED32_INT_TAG); - case TypeTags.UNSIGNED8_INT_TAG: - return sourceTypeTag == TypeTags.BYTE_TAG || sourceTypeTag == TypeTags.UNSIGNED8_INT_TAG; - case TypeTags.UNSIGNED16_INT_TAG: - return sourceTypeTag == TypeTags.BYTE_TAG || sourceTypeTag == TypeTags.UNSIGNED8_INT_TAG || - sourceTypeTag == TypeTags.UNSIGNED16_INT_TAG; - case TypeTags.UNSIGNED32_INT_TAG: - return sourceTypeTag == TypeTags.BYTE_TAG || sourceTypeTag == TypeTags.UNSIGNED8_INT_TAG || - sourceTypeTag == TypeTags.UNSIGNED16_INT_TAG || sourceTypeTag == TypeTags.UNSIGNED32_INT_TAG; - case TypeTags.ANY_TAG: - return checkIsAnyType(sourceType); - case TypeTags.ANYDATA_TAG: - return sourceType.isAnydata(); - case TypeTags.SERVICE_TAG: - return checkIsServiceType(sourceType, targetType, - unresolvedTypes == null ? new ArrayList<>() : unresolvedTypes); - case TypeTags.HANDLE_TAG: - return sourceTypeTag == TypeTags.HANDLE_TAG; - case TypeTags.READONLY_TAG: - return checkIsType(sourceType, PredefinedTypes.ANY_AND_READONLY_OR_ERROR_TYPE, unresolvedTypes); - case TypeTags.XML_ELEMENT_TAG: - case TypeTags.XML_COMMENT_TAG: - case TypeTags.XML_PI_TAG: - return targetTypeTag == sourceTypeTag; - case TypeTags.INTERSECTION_TAG: - return checkIsType(sourceType, ((BIntersectionType) targetType).getEffectiveType(), unresolvedTypes); - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return checkIsType(sourceType, ((BTypeReferenceType) targetType).getReferredType(), unresolvedTypes); - default: - return checkIsRecursiveType(sourceType, targetType, - unresolvedTypes == null ? new ArrayList<>() : unresolvedTypes); - } + // Private methods + + private enum TypeCheckResult { + TRUE, + FALSE, + MAYBE } - private static boolean checkIsType(Object sourceVal, Type sourceType, Type targetType, - List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - targetType = getImpliedType(targetType); - - int sourceTypeTag = sourceType.getTag(); - int targetTypeTag = targetType.getTag(); - - // If the source type is neither a record type nor an object type, check `is` type by looking only at the types. - // Else, since records and objects may have `readonly` or `final` fields, need to use the value also. - // e.g., - // const HUNDRED = 100; - // - // type Foo record { - // HUNDRED i; - // }; - // - // type Bar record { - // readonly string|int i; - // }; - // - // where `Bar b = {i: 100};`, `b is Foo` should evaluate to true. - if (sourceTypeTag != TypeTags.RECORD_TYPE_TAG && sourceTypeTag != TypeTags.OBJECT_TYPE_TAG) { - return checkIsType(sourceType, targetType); + private static TypeCheckResult isSubType(Type source, Type target) { + if (source instanceof ParameterizedType sourceParamType) { + if (target instanceof ParameterizedType targetParamType) { + return isSubType(sourceParamType.getParamValueType(), targetParamType.getParamValueType()); + } + return isSubType(sourceParamType.getParamValueType(), target); } + return isSubTypeInner(Builder.from(source), Builder.from(target)); + } - if (sourceType == targetType || (sourceType.getTag() == targetType.getTag() && sourceType.equals(targetType))) { - return true; + private static TypeCheckResult isSubTypeInner(SemType source, SemType target) { + if (!Core.containsBasicType(source, B_TYPE_TOP)) { + return Core.isSubType(cx, source, target) ? TypeCheckResult.TRUE : TypeCheckResult.FALSE; } - - if (targetType.isReadOnly() && !sourceType.isReadOnly()) { - return false; + if (!Core.containsBasicType(target, B_TYPE_TOP)) { + return TypeCheckResult.FALSE; } + SemType sourcePureSemType = Core.intersect(source, SEMTYPE_TOP); + SemType targetPureSemType = Core.intersect(target, SEMTYPE_TOP); + return Core.isSubType(cx, sourcePureSemType, targetPureSemType) ? TypeCheckResult.MAYBE : TypeCheckResult.FALSE; + } - switch (targetTypeTag) { - case TypeTags.ANY_TAG: - return checkIsAnyType(sourceType); - case TypeTags.READONLY_TAG: - return isInherentlyImmutableType(sourceType) || sourceType.isReadOnly(); - default: - return checkIsRecursiveTypeOnValue(sourceVal, sourceType, targetType, sourceTypeTag, targetTypeTag, - unresolvedTypes == null ? new ArrayList<>() : unresolvedTypes); + private static SemType basicType(Object value) { + if (value == null) { + return Builder.nilType(); + } else if (value instanceof Double) { + return Builder.floatType(); + } else if (value instanceof Number) { + return Builder.intType(); + } else if (value instanceof BString) { + return Builder.stringType(); + } else if (value instanceof Boolean) { + return Builder.booleanType(); + } else if (value instanceof DecimalValue) { + return Builder.decimalType(); + } else { + return Builder.bType(); } } - // Private methods - - private static boolean checkTypeDescType(Type sourceType, BTypedescType targetType, - List unresolvedTypes) { - if (sourceType.getTag() != TypeTags.TYPEDESC_TAG) { - return false; - } + private static BType bTypePart(Type t) { + return bTypePart(Builder.from(t)); + } - BTypedescType sourceTypedesc = (BTypedescType) sourceType; - return checkIsType(sourceTypedesc.getConstraint(), targetType.getConstraint(), unresolvedTypes); + private static BType bTypePart(SemType t) { + return (BType) Core.subTypeData(t, BasicTypeCode.BT_B_TYPE); } - private static boolean checkIsRecursiveType(Type sourceType, Type targetType, List unresolvedTypes) { - switch (targetType.getTag()) { - case TypeTags.MAP_TAG: - return checkIsMapType(sourceType, (BMapType) targetType, unresolvedTypes); - case TypeTags.STREAM_TAG: - return checkIsStreamType(sourceType, (BStreamType) targetType, unresolvedTypes); - case TypeTags.TABLE_TAG: - return checkIsTableType(sourceType, (BTableType) targetType, unresolvedTypes); - case TypeTags.JSON_TAG: - return checkIsJSONType(sourceType, unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG: - return checkIsRecordType(sourceType, (BRecordType) targetType, unresolvedTypes); - case TypeTags.FUNCTION_POINTER_TAG: - return checkIsFunctionType(sourceType, (BFunctionType) targetType); - case TypeTags.ARRAY_TAG: - return checkIsArrayType(sourceType, (BArrayType) targetType, unresolvedTypes); - case TypeTags.TUPLE_TAG: - return checkIsTupleType(sourceType, (BTupleType) targetType, unresolvedTypes); - case TypeTags.UNION_TAG: - return checkIsUnionType(sourceType, (BUnionType) targetType, unresolvedTypes); - case TypeTags.OBJECT_TYPE_TAG: - return checkObjectEquivalency(sourceType, (BObjectType) targetType, unresolvedTypes); - case TypeTags.FINITE_TYPE_TAG: - return checkIsFiniteType(sourceType, (BFiniteType) targetType); - case TypeTags.FUTURE_TAG: - return checkIsFutureType(sourceType, (BFutureType) targetType, unresolvedTypes); + public static boolean isInherentlyImmutableType(Type sourceType) { + sourceType = getImpliedType(sourceType); + if (FallbackTypeChecker.isSimpleBasicType(sourceType)) { + return true; + } + + switch (sourceType.getTag()) { + case TypeTags.XML_TEXT_TAG: + case TypeTags.FINITE_TYPE_TAG: // Assuming a finite type will only have members from simple basic types. + case TypeTags.READONLY_TAG: + case TypeTags.NULL_TAG: + case TypeTags.NEVER_TAG: case TypeTags.ERROR_TAG: - return checkIsErrorType(sourceType, (BErrorType) targetType, unresolvedTypes); + case TypeTags.INVOKABLE_TAG: + case TypeTags.SERVICE_TAG: case TypeTags.TYPEDESC_TAG: - return checkTypeDescType(sourceType, (BTypedescType) targetType, unresolvedTypes); + case TypeTags.FUNCTION_POINTER_TAG: + case TypeTags.HANDLE_TAG: + case TypeTags.REG_EXP_TYPE_TAG: + return true; case TypeTags.XML_TAG: - return checkIsXMLType(sourceType, targetType, unresolvedTypes); + return ((BXmlType) sourceType).constraint.getTag() == TypeTags.NEVER_TAG; + case TypeTags.TYPE_REFERENCED_TYPE_TAG: + return isInherentlyImmutableType(((BTypeReferenceType) sourceType).getReferredType()); default: - // other non-recursive types shouldn't reach here return false; } } - private static boolean checkIsRecursiveTypeOnValue(Object sourceVal, Type sourceType, Type targetType, - int sourceTypeTag, int targetTypeTag, - List unresolvedTypes) { - switch (targetTypeTag) { + public static boolean isSelectivelyImmutableType(Type type, Set unresolvedTypes) { + if (!unresolvedTypes.add(type)) { + return true; + } + + switch (type.getTag()) { + case TypeTags.ANY_TAG: case TypeTags.ANYDATA_TAG: - if (sourceTypeTag == TypeTags.OBJECT_TYPE_TAG) { + case TypeTags.JSON_TAG: + case TypeTags.XML_TAG: + case TypeTags.XML_COMMENT_TAG: + case TypeTags.XML_ELEMENT_TAG: + case TypeTags.XML_PI_TAG: + return true; + case TypeTags.ARRAY_TAG: + Type elementType = ((BArrayType) type).getElementType(); + return isInherentlyImmutableType(elementType) || + isSelectivelyImmutableType(elementType, unresolvedTypes); + case TypeTags.TUPLE_TAG: + BTupleType tupleType = (BTupleType) type; + for (Type tupMemType : tupleType.getTupleTypes()) { + if (!isInherentlyImmutableType(tupMemType) && + !isSelectivelyImmutableType(tupMemType, unresolvedTypes)) { + return false; + } + } + + Type tupRestType = tupleType.getRestType(); + if (tupRestType == null) { + return true; + } + + return isInherentlyImmutableType(tupRestType) || + isSelectivelyImmutableType(tupRestType, unresolvedTypes); + case TypeTags.RECORD_TYPE_TAG: + BRecordType recordType = (BRecordType) type; + for (Field field : recordType.getFields().values()) { + Type fieldType = field.getFieldType(); + if (!isInherentlyImmutableType(fieldType) && + !isSelectivelyImmutableType(fieldType, unresolvedTypes)) { + return false; + } + } + + Type recordRestType = recordType.restFieldType; + if (recordRestType == null) { + return true; + } + + return isInherentlyImmutableType(recordRestType) || + isSelectivelyImmutableType(recordRestType, unresolvedTypes); + case TypeTags.OBJECT_TYPE_TAG: + BObjectType objectType = (BObjectType) type; + + if (SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.CLASS) && + !SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.READONLY)) { return false; } - return checkRecordBelongsToAnydataType((MapValue) sourceVal, (BRecordType) sourceType, unresolvedTypes); + + for (Field field : objectType.getFields().values()) { + Type fieldType = field.getFieldType(); + if (!isInherentlyImmutableType(fieldType) && + !isSelectivelyImmutableType(fieldType, unresolvedTypes)) { + return false; + } + } + return true; case TypeTags.MAP_TAG: - return checkIsMapType(sourceVal, sourceType, (BMapType) targetType, unresolvedTypes); - case TypeTags.JSON_TAG: - return checkIsMapType(sourceVal, sourceType, - new BMapType(targetType.isReadOnly() ? TYPE_READONLY_JSON : - TYPE_JSON), unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG: - return checkIsRecordType(sourceVal, sourceType, (BRecordType) targetType, unresolvedTypes); + Type constraintType = ((BMapType) type).getConstrainedType(); + return isInherentlyImmutableType(constraintType) || + isSelectivelyImmutableType(constraintType, unresolvedTypes); + case TypeTags.TABLE_TAG: + Type tableConstraintType = ((BTableType) type).getConstrainedType(); + return isInherentlyImmutableType(tableConstraintType) || + isSelectivelyImmutableType(tableConstraintType, unresolvedTypes); case TypeTags.UNION_TAG: - for (Type type : ((BUnionType) targetType).getMemberTypes()) { - if (checkIsType(sourceVal, sourceType, type, unresolvedTypes)) { - return true; + boolean readonlyIntersectionExists = false; + for (Type memberType : ((BUnionType) type).getMemberTypes()) { + if (isInherentlyImmutableType(memberType) || + isSelectivelyImmutableType(memberType, unresolvedTypes)) { + readonlyIntersectionExists = true; + break; } } - return false; - case TypeTags.OBJECT_TYPE_TAG: - return checkObjectEquivalency(sourceVal, sourceType, (BObjectType) targetType, unresolvedTypes); + return readonlyIntersectionExists; + case TypeTags.INTERSECTION_TAG: + return isSelectivelyImmutableType(((BIntersectionType) type).getEffectiveType(), unresolvedTypes); + case TypeTags.TYPE_REFERENCED_TYPE_TAG: + return isSelectivelyImmutableType(((BTypeReferenceType) type).getReferredType(), unresolvedTypes); default: return false; } } - private static boolean isFiniteTypeMatch(BFiniteType sourceType, Type targetType) { - for (Object bValue : sourceType.valueSpace) { - if (!checkIsType(bValue, targetType)) { - return false; - } - } - return true; - } + static boolean isSigned32LiteralValue(Long longObject) { - private static boolean isUnionTypeMatch(BUnionType sourceType, Type targetType, List unresolvedTypes) { - for (Type type : sourceType.getMemberTypes()) { - if (!checkIsType(type, targetType, unresolvedTypes)) { - return false; - } - } - return true; - } - - private static boolean checkIsUnionType(Type sourceType, BUnionType targetType, List unresolvedTypes) { - // If we encounter two types that we are still resolving, then skip it. - // This is done to avoid recursive checking of the same type. - sourceType = getImpliedType(sourceType); - TypePair pair = new TypePair(sourceType, targetType); - if (unresolvedTypes.contains(pair)) { - return true; - } - unresolvedTypes.add(pair); - - switch (sourceType.getTag()) { - case TypeTags.UNION_TAG: - case TypeTags.JSON_TAG: - case TypeTags.ANYDATA_TAG: - return isUnionTypeMatch((BUnionType) sourceType, targetType, unresolvedTypes); - case TypeTags.FINITE_TYPE_TAG: - return isFiniteTypeMatch((BFiniteType) sourceType, targetType); - default: - for (Type type : targetType.getMemberTypes()) { - if (checkIsType(sourceType, type, unresolvedTypes)) { - return true; - } - } - return false; - - } - } - - private static boolean checkIsMapType(Type sourceType, BMapType targetType, List unresolvedTypes) { - Type targetConstrainedType = targetType.getConstrainedType(); - sourceType = getImpliedType(sourceType); - switch (sourceType.getTag()) { - case TypeTags.MAP_TAG: - return checkConstraints(((BMapType) sourceType).getConstrainedType(), targetConstrainedType, - unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG: - BRecordType recType = (BRecordType) sourceType; - BUnionType wideTypeUnion = new BUnionType(getWideTypeComponents(recType)); - return checkConstraints(wideTypeUnion, targetConstrainedType, unresolvedTypes); - default: - return false; - } - } - - private static boolean checkIsMapType(Object sourceVal, Type sourceType, BMapType targetType, - List unresolvedTypes) { - Type targetConstrainedType = targetType.getConstrainedType(); - sourceType = getImpliedType(sourceType); - switch (sourceType.getTag()) { - case TypeTags.MAP_TAG: - return checkConstraints(((BMapType) sourceType).getConstrainedType(), targetConstrainedType, - unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG: - return checkIsMapType((MapValue) sourceVal, (BRecordType) sourceType, unresolvedTypes, - targetConstrainedType); - default: - return false; - } - } - - private static boolean checkIsMapType(MapValue sourceVal, BRecordType sourceType, List unresolvedTypes, - Type targetConstrainedType) { - for (Field field : sourceType.getFields().values()) { - if (!SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY)) { - if (!checkIsType(field.getFieldType(), targetConstrainedType, unresolvedTypes)) { - return false; - } - continue; - } - - BString name = StringUtils.fromString(field.getFieldName()); - - if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL) && !sourceVal.containsKey(name)) { - continue; - } - - if (!checkIsLikeType(sourceVal.get(name), targetConstrainedType)) { - return false; - } - } - - if (sourceType.sealed) { - return true; - } - - return checkIsType(sourceType.restFieldType, targetConstrainedType, unresolvedTypes); - } - - private static boolean checkIsXMLType(Type sourceType, Type targetType, List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - int sourceTag = sourceType.getTag(); - if (sourceTag == TypeTags.FINITE_TYPE_TAG) { - return isFiniteTypeMatch((BFiniteType) sourceType, targetType); - } - - BXmlType target = ((BXmlType) targetType); - if (sourceTag == TypeTags.XML_TAG) { - Type targetConstraint = getRecursiveTargetConstraintType(target); - BXmlType source = (BXmlType) sourceType; - if (source.constraint.getTag() == TypeTags.NEVER_TAG) { - if (targetConstraint.getTag() == TypeTags.UNION_TAG) { - return checkIsUnionType(sourceType, (BUnionType) targetConstraint, unresolvedTypes); - } - return targetConstraint.getTag() == TypeTags.XML_TEXT_TAG || - targetConstraint.getTag() == TypeTags.NEVER_TAG; - } - return checkIsType(source.constraint, targetConstraint, unresolvedTypes); - } - if (TypeTags.isXMLTypeTag(sourceTag)) { - return checkIsType(sourceType, target.constraint, unresolvedTypes); - } - return false; - } - - private static Type getRecursiveTargetConstraintType(BXmlType target) { - Type targetConstraint = getImpliedType(target.constraint); - // TODO: Revisit and check why xml>> on chained iteration - while (targetConstraint.getTag() == TypeTags.XML_TAG) { - target = (BXmlType) targetConstraint; - targetConstraint = getImpliedType(target.constraint); - } - return targetConstraint; - } - - private static List getWideTypeComponents(BRecordType recType) { - List types = new ArrayList<>(); - for (Field f : recType.getFields().values()) { - types.add(f.getFieldType()); - } - if (!recType.sealed) { - types.add(recType.restFieldType); - } - return types; - } - - private static boolean checkIsStreamType(Type sourceType, BStreamType targetType, List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - if (sourceType.getTag() != TypeTags.STREAM_TAG) { - return false; - } - return checkConstraints(((BStreamType) sourceType).getConstrainedType(), targetType.getConstrainedType(), - unresolvedTypes) - && checkConstraints(((BStreamType) sourceType).getCompletionType(), targetType.getCompletionType(), - unresolvedTypes); - } - - private static boolean checkIsTableType(Type sourceType, BTableType targetType, List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - if (sourceType.getTag() != TypeTags.TABLE_TAG) { - return false; - } - - BTableType srcTableType = (BTableType) sourceType; - - if (!checkConstraints(srcTableType.getConstrainedType(), targetType.getConstrainedType(), - unresolvedTypes)) { - return false; - } - - if (targetType.getKeyType() == null && targetType.getFieldNames().length == 0) { - return true; - } - - if (targetType.getKeyType() != null) { - if (srcTableType.getKeyType() != null && - (checkConstraints(srcTableType.getKeyType(), targetType.getKeyType(), unresolvedTypes))) { - return true; - } - - if (srcTableType.getFieldNames().length == 0) { - return false; - } - - List fieldTypes = new ArrayList<>(); - Arrays.stream(srcTableType.getFieldNames()).forEach(field -> fieldTypes - .add(Objects.requireNonNull(getTableConstraintField(srcTableType.getConstrainedType(), field)) - .getFieldType())); - - if (fieldTypes.size() == 1) { - return checkConstraints(fieldTypes.get(0), targetType.getKeyType(), unresolvedTypes); - } - - BTupleType tupleType = new BTupleType(fieldTypes); - return checkConstraints(tupleType, targetType.getKeyType(), unresolvedTypes); - } - - return Arrays.equals(srcTableType.getFieldNames(), targetType.getFieldNames()); - } - - static BField getTableConstraintField(Type constraintType, String fieldName) { - switch (constraintType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: - Map fieldList = ((BRecordType) constraintType).getFields(); - return (BField) fieldList.get(fieldName); - case TypeTags.INTERSECTION_TAG: - Type effectiveType = ((BIntersectionType) constraintType).getEffectiveType(); - return getTableConstraintField(effectiveType, fieldName); - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - Type referredType = ((BTypeReferenceType) constraintType).getReferredType(); - return getTableConstraintField(referredType, fieldName); - case TypeTags.UNION_TAG: - BUnionType unionType = (BUnionType) constraintType; - List memTypes = unionType.getMemberTypes(); - List fields = memTypes.stream().map(type -> getTableConstraintField(type, fieldName)) - .filter(Objects::nonNull).toList(); - - if (fields.size() != memTypes.size()) { - return null; - } - - if (fields.stream().allMatch(field -> isSameType(field.getFieldType(), fields.get(0).getFieldType()))) { - return fields.get(0); - } - return null; - default: - return null; - } - } - - private static boolean checkIsJSONType(Type sourceType, List unresolvedTypes) { - BJsonType jsonType = (BJsonType) TYPE_JSON; - sourceType = getImpliedType(sourceType); - // If we encounter two types that we are still resolving, then skip it. - // This is done to avoid recursive checking of the same type. - TypePair pair = new TypePair(sourceType, jsonType); - if (unresolvedTypes.contains(pair)) { - return true; - } - unresolvedTypes.add(pair); - - switch (sourceType.getTag()) { - case TypeTags.STRING_TAG: - case TypeTags.CHAR_STRING_TAG: - case TypeTags.INT_TAG: - case TypeTags.SIGNED32_INT_TAG: - case TypeTags.SIGNED16_INT_TAG: - case TypeTags.SIGNED8_INT_TAG: - case TypeTags.UNSIGNED32_INT_TAG: - case TypeTags.UNSIGNED16_INT_TAG: - case TypeTags.UNSIGNED8_INT_TAG: - case TypeTags.BYTE_TAG: - case TypeTags.FLOAT_TAG: - case TypeTags.DECIMAL_TAG: - case TypeTags.BOOLEAN_TAG: - case TypeTags.NULL_TAG: - case TypeTags.JSON_TAG: - return true; - case TypeTags.ARRAY_TAG: - // Element type of the array should be 'is type' JSON - return checkIsType(((BArrayType) sourceType).getElementType(), jsonType, unresolvedTypes); - case TypeTags.FINITE_TYPE_TAG: - return isFiniteTypeMatch((BFiniteType) sourceType, jsonType); - case TypeTags.MAP_TAG: - return checkIsType(((BMapType) sourceType).getConstrainedType(), jsonType, unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG: - BRecordType recordType = (BRecordType) sourceType; - for (Field field : recordType.getFields().values()) { - if (!checkIsJSONType(field.getFieldType(), unresolvedTypes)) { - return false; - } - } - - if (!recordType.sealed) { - return checkIsJSONType(recordType.restFieldType, unresolvedTypes); - } - return true; - case TypeTags.TUPLE_TAG: - BTupleType sourceTupleType = (BTupleType) sourceType; - for (Type memberType : sourceTupleType.getTupleTypes()) { - if (!checkIsJSONType(memberType, unresolvedTypes)) { - return false; - } - } - Type tupleRestType = sourceTupleType.getRestType(); - if (tupleRestType != null) { - return checkIsJSONType(tupleRestType, unresolvedTypes); - } - return true; - case TypeTags.UNION_TAG: - for (Type memberType : ((BUnionType) sourceType).getMemberTypes()) { - if (!checkIsJSONType(memberType, unresolvedTypes)) { - return false; - } - } - return true; - default: - return false; - } - } - - private static boolean checkIsRecordType(Type sourceType, BRecordType targetType, List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - switch (sourceType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: - return checkIsRecordType((BRecordType) sourceType, targetType, unresolvedTypes); - case TypeTags.MAP_TAG: - return checkIsRecordType((BMapType) sourceType, targetType, unresolvedTypes); - default: - return false; - } - } - - private static boolean checkIsRecordType(BRecordType sourceRecordType, BRecordType targetType, - List unresolvedTypes) { - // If we encounter two types that we are still resolving, then skip it. - // This is done to avoid recursive checking of the same type. - TypePair pair = new TypePair(sourceRecordType, targetType); - if (unresolvedTypes.contains(pair)) { - return true; - } - unresolvedTypes.add(pair); - - // Unsealed records are not equivalent to sealed records, unless their rest field type is 'never'. But - // vice-versa is allowed. - if (targetType.sealed && !sourceRecordType.sealed && (sourceRecordType.restFieldType == null || - getImpliedType(sourceRecordType.restFieldType).getTag() != TypeTags.NEVER_TAG)) { - return false; - } - - // If both are sealed check the rest field type - if (!sourceRecordType.sealed && !targetType.sealed && - !checkIsType(sourceRecordType.restFieldType, targetType.restFieldType, unresolvedTypes)) { - return false; - } - - Map sourceFields = sourceRecordType.getFields(); - Set targetFieldNames = targetType.getFields().keySet(); - - for (Map.Entry targetFieldEntry : targetType.getFields().entrySet()) { - Field targetField = targetFieldEntry.getValue(); - Field sourceField = sourceFields.get(targetFieldEntry.getKey()); - - if (sourceField == null) { - if (!SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL)) { - return false; - } - - if (!sourceRecordType.sealed && !checkIsType(sourceRecordType.restFieldType, targetField.getFieldType(), - unresolvedTypes)) { - return false; - } - - continue; - } - - if (hasIncompatibleReadOnlyFlags(targetField, sourceField)) { - return false; - } - - // If the target field is required, the source field should be required as well. - if (!SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL) - && SymbolFlags.isFlagOn(sourceField.getFlags(), SymbolFlags.OPTIONAL)) { - return false; - } - - if (!checkIsType(sourceField.getFieldType(), targetField.getFieldType(), unresolvedTypes)) { - return false; - } - } - - // If there are fields remaining in the source record, first check if it's a closed record. Closed records - // should only have the fields specified by its type. - if (targetType.sealed) { - return targetFieldNames.containsAll(sourceFields.keySet()); - } - - // If it's an open record, check if they are compatible with the rest field of the target type. - for (Map.Entry sourceFieldEntry : sourceFields.entrySet()) { - if (targetFieldNames.contains(sourceFieldEntry.getKey())) { - continue; - } - - if (!checkIsType(sourceFieldEntry.getValue().getFieldType(), targetType.restFieldType, unresolvedTypes)) { - return false; - } - } - return true; - } - - private static boolean checkIsRecordType(BMapType sourceType, BRecordType targetType, - List unresolvedTypes) { - // If we encounter two types that we are still resolving, then skip it. - // This is done to avoid recursive checking of the same type. - TypePair pair = new TypePair(sourceType, targetType); - if (unresolvedTypes.contains(pair)) { - return true; - } - unresolvedTypes.add(pair); - - if (targetType.sealed) { - return false; - } - - Type constraintType = sourceType.getConstrainedType(); - - for (Field field : targetType.getFields().values()) { - var flags = field.getFlags(); - if (!SymbolFlags.isFlagOn(flags, SymbolFlags.OPTIONAL)) { - return false; - } - - if (SymbolFlags.isFlagOn(flags, SymbolFlags.READONLY) && !sourceType.isReadOnly()) { - return false; - } - - if (!checkIsType(constraintType, field.getFieldType(), unresolvedTypes)) { - return false; - } - } - - return checkIsType(constraintType, targetType.restFieldType, unresolvedTypes); - } - - private static boolean checkRecordBelongsToAnydataType(MapValue sourceVal, BRecordType recordType, - List unresolvedTypes) { - Type targetType = TYPE_ANYDATA; - TypePair pair = new TypePair(recordType, targetType); - if (unresolvedTypes.contains(pair)) { - return true; - } - unresolvedTypes.add(pair); - - Map fields = recordType.getFields(); - - for (Map.Entry fieldEntry : fields.entrySet()) { - String fieldName = fieldEntry.getKey(); - Field field = fieldEntry.getValue(); - - if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY)) { - BString fieldNameBString = StringUtils.fromString(fieldName); - - if (SymbolFlags - .isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL) && !sourceVal.containsKey(fieldNameBString)) { - continue; - } - - if (!checkIsLikeType(sourceVal.get(fieldNameBString), targetType)) { - return false; - } - } else { - if (!checkIsType(field.getFieldType(), targetType, unresolvedTypes)) { - return false; - } - } - } - - if (recordType.sealed) { - return true; - } - - return checkIsType(recordType.restFieldType, targetType, unresolvedTypes); - } - - private static boolean checkIsRecordType(Object sourceVal, Type sourceType, BRecordType targetType, - List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - switch (sourceType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: - return checkIsRecordType((MapValue) sourceVal, (BRecordType) sourceType, targetType, unresolvedTypes); - case TypeTags.MAP_TAG: - return checkIsRecordType((BMapType) sourceType, targetType, unresolvedTypes); - default: - return false; - } - } - - private static boolean checkIsRecordType(MapValue sourceRecordValue, BRecordType sourceRecordType, - BRecordType targetType, List unresolvedTypes) { - TypePair pair = new TypePair(sourceRecordType, targetType); - if (unresolvedTypes.contains(pair)) { - return true; - } - unresolvedTypes.add(pair); - - // Unsealed records are not equivalent to sealed records, unless their rest field type is 'never'. But - // vice-versa is allowed. - if (targetType.sealed && !sourceRecordType.sealed && (sourceRecordType.restFieldType == null || - getImpliedType(sourceRecordType.restFieldType).getTag() != TypeTags.NEVER_TAG)) { - return false; - } - - // If both are sealed check the rest field type - if (!sourceRecordType.sealed && !targetType.sealed && - !checkIsType(sourceRecordType.restFieldType, targetType.restFieldType, unresolvedTypes)) { - return false; - } - - Map sourceFields = sourceRecordType.getFields(); - Set targetFieldNames = targetType.getFields().keySet(); - - for (Map.Entry targetFieldEntry : targetType.getFields().entrySet()) { - String fieldName = targetFieldEntry.getKey(); - Field targetField = targetFieldEntry.getValue(); - Field sourceField = sourceFields.get(fieldName); - - if (getImpliedType(targetField.getFieldType()).getTag() == TypeTags.NEVER_TAG && - containsInvalidNeverField(sourceField, sourceRecordType)) { - return false; - } - - if (sourceField == null) { - if (!SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL)) { - return false; - } - - if (!sourceRecordType.sealed && !checkIsType(sourceRecordType.restFieldType, targetField.getFieldType(), - unresolvedTypes)) { - return false; - } - - continue; - } - - if (hasIncompatibleReadOnlyFlags(targetField, sourceField)) { - return false; - } - - boolean optionalTargetField = SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL); - boolean optionalSourceField = SymbolFlags.isFlagOn(sourceField.getFlags(), SymbolFlags.OPTIONAL); - - if (SymbolFlags.isFlagOn(sourceField.getFlags(), SymbolFlags.READONLY)) { - BString fieldNameBString = StringUtils.fromString(fieldName); - - if (optionalSourceField && !sourceRecordValue.containsKey(fieldNameBString)) { - if (!optionalTargetField) { - return false; - } - continue; - } - - if (!checkIsLikeType(sourceRecordValue.get(fieldNameBString), targetField.getFieldType())) { - return false; - } - } else { - if (!optionalTargetField && optionalSourceField) { - return false; - } - - if (!checkIsType(sourceField.getFieldType(), targetField.getFieldType(), unresolvedTypes)) { - return false; - } - } - } - - if (targetType.sealed) { - for (String sourceFieldName : sourceFields.keySet()) { - if (targetFieldNames.contains(sourceFieldName)) { - continue; - } - - if (!checkIsNeverTypeOrStructureTypeWithARequiredNeverMember( - sourceFields.get(sourceFieldName).getFieldType())) { - return false; - } - } - return true; - } - - for (Map.Entry targetFieldEntry : sourceFields.entrySet()) { - String fieldName = targetFieldEntry.getKey(); - Field field = targetFieldEntry.getValue(); - if (targetFieldNames.contains(fieldName)) { - continue; - } - - if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY)) { - if (!checkIsLikeType(sourceRecordValue.get(StringUtils.fromString(fieldName)), - targetType.restFieldType)) { - return false; - } - } else if (!checkIsType(field.getFieldType(), targetType.restFieldType, unresolvedTypes)) { - return false; - } - } - return true; - } - - private static boolean containsInvalidNeverField(Field sourceField, BRecordType sourceRecordType) { - if (sourceField != null) { - return !containsNeverType(sourceField.getFieldType()); - } - if (sourceRecordType.isSealed()) { - return true; - } - return !containsNeverType(sourceRecordType.getRestFieldType()); - } - - private static boolean containsNeverType(Type fieldType) { - fieldType = getImpliedType(fieldType); - int fieldTag = fieldType.getTag(); - if (fieldTag == TypeTags.NEVER_TAG) { - return true; - } - if (fieldTag == TypeTags.UNION_TAG) { - List memberTypes = ((BUnionType) fieldType).getOriginalMemberTypes(); - for (Type member : memberTypes) { - if (getImpliedType(member).getTag() == TypeTags.NEVER_TAG) { - return true; - } - } - } - return false; - } - - private static boolean hasIncompatibleReadOnlyFlags(Field targetField, Field sourceField) { - return SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.READONLY) && !SymbolFlags - .isFlagOn(sourceField.getFlags(), - SymbolFlags.READONLY); - } - - private static boolean checkIsArrayType(BArrayType sourceType, BArrayType targetType, - List unresolvedTypes) { - switch (sourceType.getState()) { - case OPEN: - if (targetType.getState() != ArrayState.OPEN) { - return false; - } - break; - case CLOSED: - if (targetType.getState() == ArrayState.CLOSED && - sourceType.getSize() != targetType.getSize()) { - return false; - } - break; - default: - break; - } - return checkIsType(sourceType.getElementType(), targetType.getElementType(), unresolvedTypes); - } - - private static boolean checkIsArrayType(BTupleType sourceType, BArrayType targetType, - List unresolvedTypes) { - List tupleTypes = sourceType.getTupleTypes(); - Type sourceRestType = sourceType.getRestType(); - Type targetElementType = targetType.getElementType(); - - if (targetType.getState() == ArrayState.OPEN) { - for (Type sourceElementType : tupleTypes) { - if (!checkIsType(sourceElementType, targetElementType, unresolvedTypes)) { - return false; - } - } - if (sourceRestType != null) { - return checkIsType(sourceRestType, targetElementType, unresolvedTypes); - } - return true; - } - if (sourceRestType != null) { - return false; - } - if (tupleTypes.size() != targetType.getSize()) { - return false; - } - for (Type sourceElementType : tupleTypes) { - if (!checkIsType(sourceElementType, targetElementType, unresolvedTypes)) { - return false; - } - } - return true; - } - - private static boolean checkIsArrayType(Type sourceType, BArrayType targetType, List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - int sourceTypeTag = sourceType.getTag(); - - if (sourceTypeTag == TypeTags.UNION_TAG) { - for (Type memberType : ((BUnionType) sourceType).getMemberTypes()) { - if (!checkIsArrayType(memberType, targetType, unresolvedTypes)) { - return false; - } - } - return true; - } - - if (sourceTypeTag != TypeTags.ARRAY_TAG && sourceTypeTag != TypeTags.TUPLE_TAG) { - return false; - } - - if (sourceTypeTag == TypeTags.ARRAY_TAG) { - return checkIsArrayType((BArrayType) sourceType, targetType, unresolvedTypes); - } - return checkIsArrayType((BTupleType) sourceType, targetType, unresolvedTypes); - } - - private static boolean checkIsTupleType(BArrayType sourceType, BTupleType targetType, - List unresolvedTypes) { - Type sourceElementType = sourceType.getElementType(); - List targetTypes = targetType.getTupleTypes(); - Type targetRestType = targetType.getRestType(); - - switch (sourceType.getState()) { - case OPEN: - if (targetRestType == null) { - return false; - } - if (targetTypes.isEmpty()) { - return checkIsType(sourceElementType, targetRestType, unresolvedTypes); - } - return false; - case CLOSED: - if (sourceType.getSize() < targetTypes.size()) { - return false; - } - if (targetTypes.isEmpty()) { - if (targetRestType != null) { - return checkIsType(sourceElementType, targetRestType, unresolvedTypes); - } - return sourceType.getSize() == 0; - } - - for (Type targetElementType : targetTypes) { - if (!(checkIsType(sourceElementType, targetElementType, unresolvedTypes))) { - return false; - } - } - if (sourceType.getSize() == targetTypes.size()) { - return true; - } - if (targetRestType != null) { - return checkIsType(sourceElementType, targetRestType, unresolvedTypes); - } - return false; - default: - return false; - } - } - - private static boolean checkIsTupleType(BTupleType sourceType, BTupleType targetType, - List unresolvedTypes) { - List sourceTypes = sourceType.getTupleTypes(); - Type sourceRestType = sourceType.getRestType(); - List targetTypes = targetType.getTupleTypes(); - Type targetRestType = targetType.getRestType(); - - if (sourceRestType != null && targetRestType == null) { - return false; - } - int sourceTypeSize = sourceTypes.size(); - int targetTypeSize = targetTypes.size(); - - if (sourceRestType == null && targetRestType == null && sourceTypeSize != targetTypeSize) { - return false; - } - - if (sourceTypeSize < targetTypeSize) { - return false; - } - - for (int i = 0; i < targetTypeSize; i++) { - if (!checkIsType(sourceTypes.get(i), targetTypes.get(i), unresolvedTypes)) { - return false; - } - } - if (sourceTypeSize == targetTypeSize) { - if (sourceRestType != null) { - return checkIsType(sourceRestType, targetRestType, unresolvedTypes); - } - return true; - } - - for (int i = targetTypeSize; i < sourceTypeSize; i++) { - if (!checkIsType(sourceTypes.get(i), targetRestType, unresolvedTypes)) { - return false; - } - } - if (sourceRestType != null) { - return checkIsType(sourceRestType, targetRestType, unresolvedTypes); - } - return true; - } - - private static boolean checkIsTupleType(Type sourceType, BTupleType targetType, List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - int sourceTypeTag = sourceType.getTag(); - - if (sourceTypeTag == TypeTags.UNION_TAG) { - for (Type memberType : ((BUnionType) sourceType).getMemberTypes()) { - if (!checkIsTupleType(memberType, targetType, unresolvedTypes)) { - return false; - } - } - return true; - } - - if (sourceTypeTag != TypeTags.ARRAY_TAG && sourceTypeTag != TypeTags.TUPLE_TAG) { - return false; - } - - if (sourceTypeTag == TypeTags.ARRAY_TAG) { - return checkIsTupleType((BArrayType) sourceType, targetType, unresolvedTypes); - } - return checkIsTupleType((BTupleType) sourceType, targetType, unresolvedTypes); - } - - private static boolean checkIsAnyType(Type sourceType) { - sourceType = getImpliedType(sourceType); - switch (sourceType.getTag()) { - case TypeTags.ERROR_TAG: - case TypeTags.READONLY_TAG: - return false; - case TypeTags.UNION_TAG: - case TypeTags.ANYDATA_TAG: - case TypeTags.JSON_TAG: - for (Type memberType : ((BUnionType) sourceType).getMemberTypes()) { - if (!checkIsAnyType(memberType)) { - return false; - } - } - return true; - default: - return true; - } - } - - private static boolean checkIsFiniteType(Type sourceType, BFiniteType targetType) { - sourceType = getImpliedType(sourceType); - if (sourceType.getTag() != TypeTags.FINITE_TYPE_TAG) { - return false; - } - - BFiniteType sourceFiniteType = (BFiniteType) sourceType; - if (sourceFiniteType.valueSpace.size() != targetType.valueSpace.size()) { - return false; - } - - return targetType.valueSpace.containsAll(sourceFiniteType.valueSpace); - } - - private static boolean checkIsFutureType(Type sourceType, BFutureType targetType, List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - if (sourceType.getTag() != TypeTags.FUTURE_TAG) { - return false; - } - return checkConstraints(((BFutureType) sourceType).getConstrainedType(), targetType.getConstrainedType(), - unresolvedTypes); - } - - private static boolean checkObjectEquivalency(Type sourceType, BObjectType targetType, - List unresolvedTypes) { - return checkObjectEquivalency(null, sourceType, targetType, unresolvedTypes); - } - - private static boolean checkObjectEquivalency(Object sourceVal, Type sourceType, BObjectType targetType, - List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - if (sourceType.getTag() != TypeTags.OBJECT_TYPE_TAG && sourceType.getTag() != TypeTags.SERVICE_TAG) { - return false; - } - // If we encounter two types that we are still resolving, then skip it. - // This is done to avoid recursive checking of the same type. - TypePair pair = new TypePair(sourceType, targetType); - if (unresolvedTypes.contains(pair)) { - return true; - } - unresolvedTypes.add(pair); - - BObjectType sourceObjectType = (BObjectType) sourceType; - - if (SymbolFlags.isFlagOn(targetType.flags, SymbolFlags.ISOLATED) && - !SymbolFlags.isFlagOn(sourceObjectType.flags, SymbolFlags.ISOLATED)) { - return false; - } - - Map targetFields = targetType.getFields(); - Map sourceFields = sourceObjectType.getFields(); - List targetFuncs = getAllFunctionsList(targetType); - List sourceFuncs = getAllFunctionsList(sourceObjectType); - - if (targetType.getFields().values().stream().anyMatch(field -> SymbolFlags - .isFlagOn(field.getFlags(), SymbolFlags.PRIVATE)) - || targetFuncs.stream().anyMatch(func -> SymbolFlags.isFlagOn(func.getFlags(), - SymbolFlags.PRIVATE))) { - return false; - } - - if (targetFields.size() > sourceFields.size() || targetFuncs.size() > sourceFuncs.size()) { - return false; - } - - String targetTypeModule = Optional.ofNullable(targetType.getPackage()).map(Module::toString).orElse(""); - String sourceTypeModule = Optional.ofNullable(sourceObjectType.getPackage()).map(Module::toString).orElse(""); - - if (sourceVal == null) { - if (!checkObjectSubTypeForFields(targetFields, sourceFields, targetTypeModule, sourceTypeModule, - unresolvedTypes)) { - return false; - } - } else if (!checkObjectSubTypeForFieldsByValue(targetFields, sourceFields, targetTypeModule, sourceTypeModule, - (BObject) sourceVal, unresolvedTypes)) { - return false; - } - - return checkObjectSubTypeForMethods(unresolvedTypes, targetFuncs, sourceFuncs, targetTypeModule, - sourceTypeModule, sourceObjectType, targetType); - } - - private static List getAllFunctionsList(BObjectType objectType) { - List functionList = new ArrayList<>(Arrays.asList(objectType.getMethods())); - if (objectType.getTag() == TypeTags.SERVICE_TAG || - (objectType.flags & SymbolFlags.CLIENT) == SymbolFlags.CLIENT) { - Collections.addAll(functionList, ((BNetworkObjectType) objectType).getResourceMethods()); - } - - return functionList; - } - - private static boolean checkObjectSubTypeForFields(Map targetFields, - Map sourceFields, String targetTypeModule, - String sourceTypeModule, List unresolvedTypes) { - for (Field lhsField : targetFields.values()) { - Field rhsField = sourceFields.get(lhsField.getFieldName()); - if (rhsField == null || - !isInSameVisibilityRegion(targetTypeModule, sourceTypeModule, lhsField.getFlags(), - rhsField.getFlags()) || hasIncompatibleReadOnlyFlags(lhsField, - rhsField) || - !checkIsType(rhsField.getFieldType(), lhsField.getFieldType(), unresolvedTypes)) { - return false; - } - } - return true; - } - - private static boolean checkObjectSubTypeForFieldsByValue(Map targetFields, - Map sourceFields, String targetTypeModule, - String sourceTypeModule, BObject sourceObjVal, - List unresolvedTypes) { - for (Field lhsField : targetFields.values()) { - String name = lhsField.getFieldName(); - Field rhsField = sourceFields.get(name); - if (rhsField == null || - !isInSameVisibilityRegion(targetTypeModule, sourceTypeModule, lhsField.getFlags(), - rhsField.getFlags()) || hasIncompatibleReadOnlyFlags(lhsField, - rhsField)) { - return false; - } - - if (SymbolFlags.isFlagOn(rhsField.getFlags(), SymbolFlags.FINAL)) { - Object fieldValue = sourceObjVal.get(StringUtils.fromString(name)); - Type fieldValueType = getType(fieldValue); - - if (fieldValueType.isReadOnly()) { - if (!checkIsLikeType(fieldValue, lhsField.getFieldType())) { - return false; - } - continue; - } - - if (!checkIsType(fieldValueType, lhsField.getFieldType(), unresolvedTypes)) { - return false; - } - } else if (!checkIsType(rhsField.getFieldType(), lhsField.getFieldType(), unresolvedTypes)) { - return false; - } - } - return true; - } - - private static boolean checkObjectSubTypeForMethods(List unresolvedTypes, - List targetFuncs, - List sourceFuncs, - String targetTypeModule, String sourceTypeModule, - BObjectType sourceType, BObjectType targetType) { - for (MethodType lhsFunc : targetFuncs) { - Optional rhsFunction = getMatchingInvokableType(sourceFuncs, lhsFunc, unresolvedTypes); - if (rhsFunction.isEmpty()) { - return false; - } - - MethodType rhsFunc = rhsFunction.get(); - if (rhsFunc == null || - !isInSameVisibilityRegion(targetTypeModule, sourceTypeModule, lhsFunc.getFlags(), - rhsFunc.getFlags())) { - return false; - } - if (SymbolFlags.isFlagOn(lhsFunc.getFlags(), SymbolFlags.REMOTE) != SymbolFlags - .isFlagOn(rhsFunc.getFlags(), SymbolFlags.REMOTE)) { - return false; - } - } - - // Target type is not a distinct type, no need to match type-ids - BTypeIdSet targetTypeIdSet = targetType.typeIdSet; - if (targetTypeIdSet == null) { - return true; - } - - BTypeIdSet sourceTypeIdSet = sourceType.typeIdSet; - if (sourceTypeIdSet == null) { - return false; - } - - return sourceTypeIdSet.containsAll(targetTypeIdSet); - } - - private static boolean isInSameVisibilityRegion(String lhsTypePkg, String rhsTypePkg, long lhsFlags, - long rhsFlags) { - if (SymbolFlags.isFlagOn(lhsFlags, SymbolFlags.PRIVATE)) { - return lhsTypePkg.equals(rhsTypePkg); - } else if (SymbolFlags.isFlagOn(lhsFlags, SymbolFlags.PUBLIC)) { - return SymbolFlags.isFlagOn(rhsFlags, SymbolFlags.PUBLIC); - } - return !SymbolFlags.isFlagOn(rhsFlags, SymbolFlags.PRIVATE) && !SymbolFlags - .isFlagOn(rhsFlags, SymbolFlags.PUBLIC) && - lhsTypePkg.equals(rhsTypePkg); - } - - private static Optional getMatchingInvokableType(List rhsFuncs, - MethodType lhsFunc, - List unresolvedTypes) { - Optional matchingFunction = rhsFuncs.stream() - .filter(rhsFunc -> lhsFunc.getName().equals(rhsFunc.getName())) - .filter(rhsFunc -> checkFunctionTypeEqualityForObjectType(rhsFunc.getType(), lhsFunc.getType(), - unresolvedTypes)) - .findFirst(); - - if (matchingFunction.isEmpty()) { - return matchingFunction; - } - // For resource function match, we need to check whether lhs function resource path type belongs to - // rhs function resource path type - MethodType matchingFunc = matchingFunction.get(); - boolean lhsFuncIsResource = SymbolFlags.isFlagOn(lhsFunc.getFlags(), SymbolFlags.RESOURCE); - boolean matchingFuncIsResource = SymbolFlags.isFlagOn(matchingFunc.getFlags(), SymbolFlags.RESOURCE); - - if (!lhsFuncIsResource && !matchingFuncIsResource) { - return matchingFunction; - } - - if ((lhsFuncIsResource && !matchingFuncIsResource) || (matchingFuncIsResource && !lhsFuncIsResource)) { - return Optional.empty(); - } - - Type[] lhsFuncResourcePathTypes = ((BResourceMethodType) lhsFunc).pathSegmentTypes; - Type[] rhsFuncResourcePathTypes = ((BResourceMethodType) matchingFunc).pathSegmentTypes; - - int lhsFuncResourcePathTypesSize = lhsFuncResourcePathTypes.length; - if (lhsFuncResourcePathTypesSize != rhsFuncResourcePathTypes.length) { - return Optional.empty(); - } - - for (int i = 0; i < lhsFuncResourcePathTypesSize; i++) { - if (!checkIsType(lhsFuncResourcePathTypes[i], rhsFuncResourcePathTypes[i])) { - return Optional.empty(); - } - } - - return matchingFunction; - } - - private static boolean checkFunctionTypeEqualityForObjectType(FunctionType source, FunctionType target, - List unresolvedTypes) { - if (hasIncompatibleIsolatedFlags(target, source)) { - return false; - } - - if (source.getParameters().length != target.getParameters().length) { - return false; - } - - for (int i = 0; i < source.getParameters().length; i++) { - if (!checkIsType(target.getParameters()[i].type, source.getParameters()[i].type, unresolvedTypes)) { - return false; - } - } - - if (source.getReturnType() == null && target.getReturnType() == null) { - return true; - } else if (source.getReturnType() == null || target.getReturnType() == null) { - return false; - } - - return checkIsType(source.getReturnType(), target.getReturnType(), unresolvedTypes); - } - - private static boolean checkIsFunctionType(Type sourceType, BFunctionType targetType) { - sourceType = getImpliedType(sourceType); - if (sourceType.getTag() != TypeTags.FUNCTION_POINTER_TAG) { - return false; - } - - BFunctionType source = (BFunctionType) sourceType; - if (hasIncompatibleIsolatedFlags(targetType, source) || hasIncompatibleTransactionalFlags(targetType, source)) { - return false; - } - - if (SymbolFlags.isFlagOn(targetType.getFlags(), SymbolFlags.ANY_FUNCTION)) { - return true; - } - - if (source.parameters.length != targetType.parameters.length) { - return false; - } - - for (int i = 0; i < source.parameters.length; i++) { - if (!checkIsType(targetType.parameters[i].type, source.parameters[i].type, new ArrayList<>())) { - return false; - } - } - - return checkIsType(source.retType, targetType.retType, new ArrayList<>()); - } - - private static boolean hasIncompatibleIsolatedFlags(FunctionType target, FunctionType source) { - return SymbolFlags.isFlagOn(target.getFlags(), SymbolFlags.ISOLATED) && !SymbolFlags - .isFlagOn(source.getFlags(), SymbolFlags.ISOLATED); - } - - private static boolean hasIncompatibleTransactionalFlags(FunctionType target, FunctionType source) { - return SymbolFlags.isFlagOn(source.getFlags(), SymbolFlags.TRANSACTIONAL) && !SymbolFlags - .isFlagOn(target.getFlags(), SymbolFlags.TRANSACTIONAL); - } - - private static boolean checkIsServiceType(Type sourceType, Type targetType, List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - if (sourceType.getTag() == TypeTags.SERVICE_TAG) { - return checkObjectEquivalency(sourceType, (BObjectType) targetType, unresolvedTypes); - } - - if (sourceType.getTag() == TypeTags.OBJECT_TYPE_TAG) { - var flags = ((BObjectType) sourceType).flags; - return (flags & SymbolFlags.SERVICE) == SymbolFlags.SERVICE; - } - - return false; - } - - public static boolean isInherentlyImmutableType(Type sourceType) { - sourceType = getImpliedType(sourceType); - if (isSimpleBasicType(sourceType)) { - return true; - } - - switch (sourceType.getTag()) { - case TypeTags.XML_TEXT_TAG: - case TypeTags.FINITE_TYPE_TAG: // Assuming a finite type will only have members from simple basic types. - case TypeTags.READONLY_TAG: - case TypeTags.NULL_TAG: - case TypeTags.NEVER_TAG: - case TypeTags.ERROR_TAG: - case TypeTags.INVOKABLE_TAG: - case TypeTags.SERVICE_TAG: - case TypeTags.TYPEDESC_TAG: - case TypeTags.FUNCTION_POINTER_TAG: - case TypeTags.HANDLE_TAG: - case TypeTags.REG_EXP_TYPE_TAG: - return true; - case TypeTags.XML_TAG: - return ((BXmlType) sourceType).constraint.getTag() == TypeTags.NEVER_TAG; - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return isInherentlyImmutableType(((BTypeReferenceType) sourceType).getReferredType()); - default: - return false; - } - } - - public static boolean isSelectivelyImmutableType(Type type, Set unresolvedTypes) { - if (!unresolvedTypes.add(type)) { - return true; - } - - switch (type.getTag()) { - case TypeTags.ANY_TAG: - case TypeTags.ANYDATA_TAG: - case TypeTags.JSON_TAG: - case TypeTags.XML_TAG: - case TypeTags.XML_COMMENT_TAG: - case TypeTags.XML_ELEMENT_TAG: - case TypeTags.XML_PI_TAG: - return true; - case TypeTags.ARRAY_TAG: - Type elementType = ((BArrayType) type).getElementType(); - return isInherentlyImmutableType(elementType) || - isSelectivelyImmutableType(elementType, unresolvedTypes); - case TypeTags.TUPLE_TAG: - BTupleType tupleType = (BTupleType) type; - for (Type tupMemType : tupleType.getTupleTypes()) { - if (!isInherentlyImmutableType(tupMemType) && - !isSelectivelyImmutableType(tupMemType, unresolvedTypes)) { - return false; - } - } - - Type tupRestType = tupleType.getRestType(); - if (tupRestType == null) { - return true; - } - - return isInherentlyImmutableType(tupRestType) || - isSelectivelyImmutableType(tupRestType, unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG: - BRecordType recordType = (BRecordType) type; - for (Field field : recordType.getFields().values()) { - Type fieldType = field.getFieldType(); - if (!isInherentlyImmutableType(fieldType) && - !isSelectivelyImmutableType(fieldType, unresolvedTypes)) { - return false; - } - } - - Type recordRestType = recordType.restFieldType; - if (recordRestType == null) { - return true; - } - - return isInherentlyImmutableType(recordRestType) || - isSelectivelyImmutableType(recordRestType, unresolvedTypes); - case TypeTags.OBJECT_TYPE_TAG: - BObjectType objectType = (BObjectType) type; - - if (SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.CLASS) && - !SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.READONLY)) { - return false; - } - - for (Field field : objectType.getFields().values()) { - Type fieldType = field.getFieldType(); - if (!isInherentlyImmutableType(fieldType) && - !isSelectivelyImmutableType(fieldType, unresolvedTypes)) { - return false; - } - } - return true; - case TypeTags.MAP_TAG: - Type constraintType = ((BMapType) type).getConstrainedType(); - return isInherentlyImmutableType(constraintType) || - isSelectivelyImmutableType(constraintType, unresolvedTypes); - case TypeTags.TABLE_TAG: - Type tableConstraintType = ((BTableType) type).getConstrainedType(); - return isInherentlyImmutableType(tableConstraintType) || - isSelectivelyImmutableType(tableConstraintType, unresolvedTypes); - case TypeTags.UNION_TAG: - boolean readonlyIntersectionExists = false; - for (Type memberType : ((BUnionType) type).getMemberTypes()) { - if (isInherentlyImmutableType(memberType) || - isSelectivelyImmutableType(memberType, unresolvedTypes)) { - readonlyIntersectionExists = true; - break; - } - } - return readonlyIntersectionExists; - case TypeTags.INTERSECTION_TAG: - return isSelectivelyImmutableType(((BIntersectionType) type).getEffectiveType(), unresolvedTypes); - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return isSelectivelyImmutableType(((BTypeReferenceType) type).getReferredType(), unresolvedTypes); - default: - return false; - } - } - - private static boolean checkConstraints(Type sourceConstraint, Type targetConstraint, - List unresolvedTypes) { - if (sourceConstraint == null) { - sourceConstraint = TYPE_ANY; - } - - if (targetConstraint == null) { - targetConstraint = TYPE_ANY; - } - - return checkIsType(sourceConstraint, targetConstraint, unresolvedTypes); - } - - private static boolean isMutable(Object value, Type sourceType) { - // All the value types are immutable - sourceType = getImpliedType(sourceType); - if (value == null || sourceType.getTag() < TypeTags.NULL_TAG || - sourceType.getTag() == TypeTags.FINITE_TYPE_TAG) { - return false; - } - - return !((BRefValue) value).isFrozen(); - } - - private static boolean checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(Type type) { - Set visitedTypeSet = new HashSet<>(); - return checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(type, visitedTypeSet); - } - - private static boolean checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(Type type, - Set visitedTypeSet) { - switch (type.getTag()) { - case TypeTags.NEVER_TAG: - return true; - case TypeTags.RECORD_TYPE_TAG: - BRecordType recordType = (BRecordType) type; - visitedTypeSet.add(recordType.getName()); - for (Field field : recordType.getFields().values()) { - // skip check for fields with self referencing type and not required fields. - if ((SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED) || - !SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL)) && - !visitedTypeSet.contains(field.getFieldType()) && - checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(field.getFieldType(), - visitedTypeSet)) { - return true; - } - } - return false; - case TypeTags.TUPLE_TAG: - BTupleType tupleType = (BTupleType) type; - visitedTypeSet.add(tupleType.getName()); - List tupleTypes = tupleType.getTupleTypes(); - for (Type mem : tupleTypes) { - if (!visitedTypeSet.add(mem.getName())) { - continue; - } - if (checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(mem, visitedTypeSet)) { - return true; - } - } - return false; - case TypeTags.ARRAY_TAG: - BArrayType arrayType = (BArrayType) type; - visitedTypeSet.add(arrayType.getName()); - Type elemType = arrayType.getElementType(); - visitedTypeSet.add(elemType.getName()); - return arrayType.getState() != ArrayState.OPEN && - checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(elemType, visitedTypeSet); - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return checkIsNeverTypeOrStructureTypeWithARequiredNeverMember( - ((BTypeReferenceType) type).getReferredType(), visitedTypeSet); - case TypeTags.INTERSECTION_TAG: - return checkIsNeverTypeOrStructureTypeWithARequiredNeverMember( - ((BIntersectionType) type).getEffectiveType(), visitedTypeSet); - default: - return false; - } - } - - /** - * Check whether a given value confirms to a given type. First it checks if the type of the value, and - * if fails then falls back to checking the value. - * - * @param errors list to collect typecast errors - * @param sourceValue Value to check - * @param targetType Target type - * @param unresolvedValues Values that are unresolved so far - * @param allowNumericConversion Flag indicating whether to perform numeric conversions - * @param varName variable name to identify the parent of a record field - * @return True if the value confirms to the provided type. False, otherwise. - */ - private static boolean checkIsLikeType(List errors, Object sourceValue, Type targetType, - List unresolvedValues, - boolean allowNumericConversion, String varName) { - Type sourceType = getType(sourceValue); - if (checkIsType(sourceType, targetType, new ArrayList<>())) { - return true; - } - - return checkIsLikeOnValue(errors, sourceValue, sourceType, targetType, unresolvedValues, allowNumericConversion, - varName); - } - - /** - * Check whether a given value confirms to a given type. Strictly checks the value only, and does not consider the - * type of the value for consideration. - * - * @param errors list to collect typecast errors - * @param sourceValue Value to check - * @param sourceType Type of the value - * @param targetType Target type - * @param unresolvedValues Values that are unresolved so far - * @param allowNumericConversion Flag indicating whether to perform numeric conversions - * @param varName variable name to identify the parent of a record field - * @return True if the value confirms to the provided type. False, otherwise. - */ - private static boolean checkIsLikeOnValue(List errors, Object sourceValue, Type sourceType, Type targetType, - List unresolvedValues, boolean allowNumericConversion, - String varName) { - int sourceTypeTag = sourceType.getTag(); - int targetTypeTag = targetType.getTag(); - - switch (sourceTypeTag) { - case TypeTags.INTERSECTION_TAG: - return checkIsLikeOnValue(errors, sourceValue, ((BIntersectionType) sourceType).getEffectiveType(), - targetTypeTag != TypeTags.INTERSECTION_TAG ? targetType : - ((BIntersectionType) targetType).getEffectiveType(), - unresolvedValues, allowNumericConversion, varName); - case TypeTags.PARAMETERIZED_TYPE_TAG: - if (targetTypeTag != TypeTags.PARAMETERIZED_TYPE_TAG) { - return checkIsLikeOnValue(errors, sourceValue, - ((BParameterizedType) sourceType).getParamValueType(), targetType, unresolvedValues, - allowNumericConversion, varName); - } - return checkIsLikeOnValue(errors, sourceValue, ((BParameterizedType) sourceType).getParamValueType(), - ((BParameterizedType) targetType).getParamValueType(), unresolvedValues, - allowNumericConversion, varName); - default: - break; - } - - switch (targetTypeTag) { - case TypeTags.READONLY_TAG: - return true; - case TypeTags.BYTE_TAG: - if (TypeTags.isIntegerTypeTag(sourceTypeTag)) { - return isByteLiteral(((Number) sourceValue).longValue()); - } - return allowNumericConversion && TypeConverter.isConvertibleToByte(sourceValue); - case TypeTags.INT_TAG: - return allowNumericConversion && TypeConverter.isConvertibleToInt(sourceValue); - case TypeTags.SIGNED32_INT_TAG: - case TypeTags.SIGNED16_INT_TAG: - case TypeTags.SIGNED8_INT_TAG: - case TypeTags.UNSIGNED32_INT_TAG: - case TypeTags.UNSIGNED16_INT_TAG: - case TypeTags.UNSIGNED8_INT_TAG: - if (TypeTags.isIntegerTypeTag(sourceTypeTag)) { - return TypeConverter.isConvertibleToIntSubType(sourceValue, targetType); - } - return allowNumericConversion && TypeConverter.isConvertibleToIntSubType(sourceValue, targetType); - case TypeTags.FLOAT_TAG: - case TypeTags.DECIMAL_TAG: - return allowNumericConversion && TypeConverter.isConvertibleToFloatingPointTypes(sourceValue); - case TypeTags.CHAR_STRING_TAG: - return TypeConverter.isConvertibleToChar(sourceValue); - case TypeTags.RECORD_TYPE_TAG: - return checkIsLikeRecordType(sourceValue, (BRecordType) targetType, unresolvedValues, - allowNumericConversion, varName, errors); - case TypeTags.TABLE_TAG: - return checkIsLikeTableType(sourceValue, (BTableType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.JSON_TAG: - return checkIsLikeJSONType(sourceValue, sourceType, (BJsonType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.MAP_TAG: - return checkIsLikeMapType(sourceValue, (BMapType) targetType, unresolvedValues, allowNumericConversion); - case TypeTags.STREAM_TAG: - return checkIsLikeStreamType(sourceValue, (BStreamType) targetType); - case TypeTags.ARRAY_TAG: - return checkIsLikeArrayType(sourceValue, (BArrayType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.TUPLE_TAG: - return checkIsLikeTupleType(sourceValue, (BTupleType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.ERROR_TAG: - return checkIsLikeErrorType(sourceValue, (BErrorType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.ANYDATA_TAG: - return checkIsLikeAnydataType(sourceValue, sourceType, unresolvedValues, allowNumericConversion); - case TypeTags.FINITE_TYPE_TAG: - return checkFiniteTypeAssignable(sourceValue, sourceType, (BFiniteType) targetType, - unresolvedValues, allowNumericConversion); - case TypeTags.XML_ELEMENT_TAG: - case TypeTags.XML_COMMENT_TAG: - case TypeTags.XML_PI_TAG: - case TypeTags.XML_TEXT_TAG: - if (TypeTags.isXMLTypeTag(sourceTypeTag)) { - return checkIsLikeXmlValueSingleton((XmlValue) sourceValue, targetType); - } - return false; - case TypeTags.XML_TAG: - if (TypeTags.isXMLTypeTag(sourceTypeTag)) { - return checkIsLikeXMLSequenceType((XmlValue) sourceValue, targetType); - } - return false; - case TypeTags.UNION_TAG: - return checkIsLikeUnionType(errors, sourceValue, (BUnionType) targetType, unresolvedValues, - allowNumericConversion, varName); - case TypeTags.INTERSECTION_TAG: - return checkIsLikeOnValue(errors, sourceValue, sourceType, - ((BIntersectionType) targetType).getEffectiveType(), unresolvedValues, allowNumericConversion, - varName); - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return checkIsLikeOnValue(errors, sourceValue, sourceType, - ((BTypeReferenceType) targetType).getReferredType(), unresolvedValues, allowNumericConversion, - varName); - default: - return false; - } - } - - private static boolean checkIsLikeUnionType(List errors, Object sourceValue, BUnionType targetType, - List unresolvedValues, boolean allowNumericConversion, - String varName) { - if (allowNumericConversion) { - List compatibleTypesWithNumConversion = new ArrayList<>(); - List compatibleTypesWithoutNumConversion = new ArrayList<>(); - for (Type type : targetType.getMemberTypes()) { - List tempList = new ArrayList<>(unresolvedValues.size()); - tempList.addAll(unresolvedValues); - - if (checkIsLikeType(null, sourceValue, type, tempList, false, varName)) { - compatibleTypesWithoutNumConversion.add(type); - } - - if (checkIsLikeType(null, sourceValue, type, unresolvedValues, true, varName)) { - compatibleTypesWithNumConversion.add(type); - } - } - // Conversion should only be possible to one other numeric type. - return !compatibleTypesWithNumConversion.isEmpty() && - compatibleTypesWithNumConversion.size() - compatibleTypesWithoutNumConversion.size() <= 1; - } else { - return checkIsLikeUnionType(errors, sourceValue, targetType, unresolvedValues, varName); - } - } - - private static boolean checkIsLikeUnionType(List errors, Object sourceValue, BUnionType targetType, - List unresolvedValues, String varName) { - if (errors == null) { - for (Type type : targetType.getMemberTypes()) { - if (checkIsLikeType(null, sourceValue, type, unresolvedValues, false, varName)) { - return true; - } - } - } else { - int initialErrorCount; - errors.add(ERROR_MESSAGE_UNION_START); - int initialErrorListSize = errors.size(); - for (Type type : targetType.getMemberTypes()) { - initialErrorCount = errors.size(); - if (checkIsLikeType(errors, sourceValue, type, unresolvedValues, false, varName)) { - errors.subList(initialErrorListSize - 1, errors.size()).clear(); - return true; - } - if (initialErrorCount != errors.size()) { - errors.add(ERROR_MESSAGE_UNION_SEPARATOR); - } - } - int currentErrorListSize = errors.size(); - errors.remove(currentErrorListSize - 1); - if (initialErrorListSize != currentErrorListSize) { - errors.add(ERROR_MESSAGE_UNION_END); - } - } - return false; - } - - private static XmlNodeType getXmlNodeType(Type type) { - switch (getImpliedType(type).getTag()) { - case TypeTags.XML_ELEMENT_TAG: - return XmlNodeType.ELEMENT; - case TypeTags.XML_COMMENT_TAG: - return XmlNodeType.COMMENT; - case TypeTags.XML_PI_TAG: - return XmlNodeType.PI; - default: - return XmlNodeType.TEXT; - } - } - - private static boolean checkIsLikeXmlValueSingleton(XmlValue xmlSource, Type targetType) { - XmlNodeType targetXmlNodeType = getXmlNodeType(targetType); - XmlNodeType xmlSourceNodeType = xmlSource.getNodeType(); - - if (xmlSourceNodeType == targetXmlNodeType) { - return true; - } - - if (xmlSourceNodeType == XmlNodeType.SEQUENCE) { - XmlSequence seq = (XmlSequence) xmlSource; - return seq.size() == 1 && seq.getChildrenList().get(0).getNodeType() == targetXmlNodeType || - (targetXmlNodeType == XmlNodeType.TEXT && seq.isEmpty()); - } - - return false; - } - - private static void populateTargetXmlNodeTypes(Set nodeTypes, Type targetType) { - // there are only 4 xml subtypes - if (nodeTypes.size() == 4) { - return; - } - - Type referredType = getImpliedType(targetType); - switch (referredType.getTag()) { - case TypeTags.UNION_TAG: - for (Type memberType : ((UnionType) referredType).getMemberTypes()) { - populateTargetXmlNodeTypes(nodeTypes, memberType); - } - break; - case TypeTags.INTERSECTION_TAG: - populateTargetXmlNodeTypes(nodeTypes, ((IntersectionType) referredType).getEffectiveType()); - break; - case TypeTags.XML_ELEMENT_TAG: - nodeTypes.add(XmlNodeType.ELEMENT); - break; - case TypeTags.XML_COMMENT_TAG: - nodeTypes.add(XmlNodeType.COMMENT); - break; - case TypeTags.XML_PI_TAG: - nodeTypes.add(XmlNodeType.PI); - break; - case TypeTags.XML_TEXT_TAG: - nodeTypes.add(XmlNodeType.TEXT); - break; - case TypeTags.XML_TAG: - populateTargetXmlNodeTypes(nodeTypes, ((BXmlType) referredType).constraint); - break; - default: - break; - - } - } - - private static boolean checkIsLikeXMLSequenceType(XmlValue xmlSource, Type targetType) { - Set acceptedNodeTypes = new HashSet<>(); - populateTargetXmlNodeTypes(acceptedNodeTypes, targetType); - - XmlNodeType xmlSourceNodeType = xmlSource.getNodeType(); - if (xmlSourceNodeType != XmlNodeType.SEQUENCE) { - return acceptedNodeTypes.contains(xmlSourceNodeType); - } - - XmlSequence seq = (XmlSequence) xmlSource; - for (BXml m : seq.getChildrenList()) { - if (!acceptedNodeTypes.contains(m.getNodeType())) { - return false; - } - } - return true; - } - - public static boolean isNumericType(Type type) { - type = getImpliedType(type); - return type.getTag() < TypeTags.STRING_TAG || TypeTags.isIntegerTypeTag(type.getTag()); - } - - private static boolean checkIsLikeAnydataType(Object sourceValue, Type sourceType, - List unresolvedValues, - boolean allowNumericConversion) { - sourceType = getImpliedType(sourceType); - switch (sourceType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: - case TypeTags.MAP_TAG: - return isLikeAnydataType(((MapValueImpl) sourceValue).values().toArray(), - unresolvedValues, allowNumericConversion); - case TypeTags.TABLE_TAG: - return isLikeAnydataType(((TableValueImpl) sourceValue).values().toArray(), - unresolvedValues, allowNumericConversion); - case TypeTags.ARRAY_TAG: - ArrayValue arr = (ArrayValue) sourceValue; - BArrayType arrayType = (BArrayType) getImpliedType(arr.getType()); - switch (getImpliedType(arrayType.getElementType()).getTag()) { - case TypeTags.INT_TAG: - case TypeTags.FLOAT_TAG: - case TypeTags.DECIMAL_TAG: - case TypeTags.STRING_TAG: - case TypeTags.BOOLEAN_TAG: - case TypeTags.BYTE_TAG: - return true; - default: - return isLikeAnydataType(arr.getValues(), unresolvedValues, allowNumericConversion); - } - case TypeTags.TUPLE_TAG: - return isLikeAnydataType(((ArrayValue) sourceValue).getValues(), unresolvedValues, - allowNumericConversion); - default: - return sourceType.isAnydata(); - } - } - - private static boolean isLikeAnydataType(Object[] objects, List unresolvedValues, - boolean allowNumericConversion) { - for (Object value : objects) { - if (!checkIsLikeType(null, value, TYPE_ANYDATA, unresolvedValues, allowNumericConversion, - null)) { - return false; - } - } - return true; - } - - private static boolean checkIsLikeTupleType(Object sourceValue, BTupleType targetType, - List unresolvedValues, boolean allowNumericConversion) { - if (!(sourceValue instanceof ArrayValue source)) { - return false; - } - - List targetTypes = targetType.getTupleTypes(); - int sourceTypeSize = source.size(); - int targetTypeSize = targetTypes.size(); - Type targetRestType = targetType.getRestType(); - - if (sourceTypeSize < targetTypeSize) { - return false; - } - if (targetRestType == null && sourceTypeSize > targetTypeSize) { - return false; - } - - for (int i = 0; i < targetTypeSize; i++) { - if (!checkIsLikeType(null, source.getRefValue(i), targetTypes.get(i), unresolvedValues, - allowNumericConversion, null)) { - return false; - } - } - for (int i = targetTypeSize; i < sourceTypeSize; i++) { - if (!checkIsLikeType(null, source.getRefValue(i), targetRestType, unresolvedValues, - allowNumericConversion, null)) { - return false; - } - } - return true; - } - - static boolean isByteLiteral(long longValue) { - return (longValue >= BBYTE_MIN_VALUE && longValue <= BBYTE_MAX_VALUE); - } - - static boolean isSigned32LiteralValue(Long longObject) { - - return (longObject >= SIGNED32_MIN_VALUE && longObject <= SIGNED32_MAX_VALUE); + return (longObject >= SIGNED32_MIN_VALUE && longObject <= SIGNED32_MAX_VALUE); } static boolean isSigned16LiteralValue(Long longObject) { @@ -2550,379 +748,6 @@ static boolean isCharLiteralValue(Object object) { return value.codePoints().count() == 1; } - private static boolean checkIsLikeArrayType(Object sourceValue, BArrayType targetType, - List unresolvedValues, boolean allowNumericConversion) { - if (!(sourceValue instanceof ArrayValue)) { - return false; - } - - ArrayValue source = (ArrayValue) sourceValue; - Type targetTypeElementType = targetType.getElementType(); - if (source.getType().getTag() == TypeTags.ARRAY_TAG) { - Type sourceElementType = ((BArrayType) source.getType()).getElementType(); - if (isValueType(sourceElementType)) { - - if (checkIsType(sourceElementType, targetTypeElementType, new ArrayList<>())) { - return true; - } - - if (allowNumericConversion && isNumericType(sourceElementType)) { - if (isNumericType(targetTypeElementType)) { - return true; - } - - if (targetTypeElementType.getTag() != TypeTags.UNION_TAG) { - return false; - } - - List targetNumericTypes = new ArrayList<>(); - for (Type memType : ((BUnionType) targetTypeElementType).getMemberTypes()) { - if (isNumericType(memType) && !targetNumericTypes.contains(memType)) { - targetNumericTypes.add(memType); - } - } - return targetNumericTypes.size() == 1; - } - - if (targetTypeElementType.getTag() == TypeTags.FLOAT_TAG || - targetTypeElementType.getTag() == TypeTags.DECIMAL_TAG) { - return false; - } - } - } - - int sourceSize = source.size(); - if ((targetType.getState() != ArrayState.OPEN) && (sourceSize != targetType.getSize())) { - return false; - } - for (int i = 0; i < sourceSize; i++) { - if (!checkIsLikeType(null, source.get(i), targetTypeElementType, unresolvedValues, - allowNumericConversion, null)) { - return false; - } - } - return true; - } - - private static boolean checkIsLikeMapType(Object sourceValue, BMapType targetType, - List unresolvedValues, boolean allowNumericConversion) { - if (!(sourceValue instanceof MapValueImpl)) { - return false; - } - - for (Object mapEntry : ((MapValueImpl) sourceValue).values()) { - if (!checkIsLikeType(null, mapEntry, targetType.getConstrainedType(), unresolvedValues, - allowNumericConversion, null)) { - return false; - } - } - return true; - } - - private static boolean checkIsLikeStreamType(Object sourceValue, BStreamType targetType) { - if (!(sourceValue instanceof StreamValue)) { - return false; - } - - BStreamType streamType = (BStreamType) ((StreamValue) sourceValue).getType(); - - return streamType.getConstrainedType() == targetType.getConstrainedType(); - } - - private static boolean checkIsLikeJSONType(Object sourceValue, Type sourceType, BJsonType targetType, - List unresolvedValues, boolean allowNumericConversion) { - Type referredSourceType = getImpliedType(sourceType); - switch (referredSourceType.getTag()) { - case TypeTags.ARRAY_TAG: - ArrayValue source = (ArrayValue) sourceValue; - Type elementType = ((BArrayType) referredSourceType).getElementType(); - if (checkIsType(elementType, targetType, new ArrayList<>())) { - return true; - } - - Object[] arrayValues = source.getValues(); - for (int i = 0; i < source.size(); i++) { - if (!checkIsLikeType(null, arrayValues[i], targetType, unresolvedValues, - allowNumericConversion, null)) { - return false; - } - } - return true; - case TypeTags.TUPLE_TAG: - for (Object obj : ((TupleValueImpl) sourceValue).getValues()) { - if (!checkIsLikeType(null, obj, targetType, unresolvedValues, allowNumericConversion, - null)) { - return false; - } - } - return true; - case TypeTags.MAP_TAG: - return checkIsMappingLikeJsonType((MapValueImpl) sourceValue, targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.RECORD_TYPE_TAG: - TypeValuePair typeValuePair = new TypeValuePair(sourceValue, targetType); - if (unresolvedValues.contains(typeValuePair)) { - return true; - } - unresolvedValues.add(typeValuePair); - return checkIsMappingLikeJsonType((MapValueImpl) sourceValue, targetType, unresolvedValues, - allowNumericConversion); - default: - return false; - } - } - - private static boolean checkIsMappingLikeJsonType(MapValueImpl sourceValue, BJsonType targetType, - List unresolvedValues, - boolean allowNumericConversion) { - for (Object value : sourceValue.values()) { - if (!checkIsLikeType(null, value, targetType, unresolvedValues, allowNumericConversion, - null)) { - return false; - } - } - return true; - } - - private static boolean checkIsLikeRecordType(Object sourceValue, BRecordType targetType, - List unresolvedValues, boolean allowNumericConversion, - String varName, List errors) { - if (!(sourceValue instanceof MapValueImpl)) { - return false; - } - - TypeValuePair typeValuePair = new TypeValuePair(sourceValue, targetType); - if (unresolvedValues.contains(typeValuePair)) { - return true; - } - unresolvedValues.add(typeValuePair); - - Map targetFieldTypes = new HashMap<>(); - Type restFieldType = targetType.restFieldType; - boolean returnVal = true; - - for (Field field : targetType.getFields().values()) { - targetFieldTypes.put(field.getFieldName(), field.getFieldType()); - } - - for (Map.Entry targetTypeEntry : targetFieldTypes.entrySet()) { - String fieldName = targetTypeEntry.getKey().toString(); - String fieldNameLong = TypeConverter.getLongFieldName(varName, fieldName); - Field targetField = targetType.getFields().get(fieldName); - - if (!(((MapValueImpl) sourceValue).containsKey(StringUtils.fromString(fieldName))) && - !SymbolFlags.isFlagOn(targetField.getFlags(), SymbolFlags.OPTIONAL)) { - addErrorMessage((errors == null) ? 0 : errors.size(), "missing required field '" + fieldNameLong + - "' of type '" + targetField.getFieldType().toString() + "' in record '" + targetType + "'", - errors); - if ((errors == null) || (errors.size() >= MAX_TYPECAST_ERROR_COUNT + 1)) { - return false; - } - returnVal = false; - } - } - - for (Object object : ((MapValueImpl) sourceValue).entrySet()) { - Map.Entry valueEntry = (Map.Entry) object; - String fieldName = valueEntry.getKey().toString(); - String fieldNameLong = TypeConverter.getLongFieldName(varName, fieldName); - int initialErrorCount = (errors == null) ? 0 : errors.size(); - - if (targetFieldTypes.containsKey(fieldName)) { - if (!checkIsLikeType(errors, (valueEntry.getValue()), targetFieldTypes.get(fieldName), - unresolvedValues, allowNumericConversion, fieldNameLong)) { - addErrorMessage(initialErrorCount, "field '" + fieldNameLong + "' in record '" + targetType + - "' should be of type '" + targetFieldTypes.get(fieldName) + "', found '" + - TypeConverter.getShortSourceValue(valueEntry.getValue()) + "'", errors); - returnVal = false; - } - } else { - if (!targetType.sealed) { - if (!checkIsLikeType(errors, (valueEntry.getValue()), restFieldType, unresolvedValues, - allowNumericConversion, fieldNameLong)) { - addErrorMessage(initialErrorCount, "value of field '" + valueEntry.getKey() + - "' adding to the record '" + targetType + "' should be of type '" + restFieldType + - "', found '" + TypeConverter.getShortSourceValue(valueEntry.getValue()) + "'", errors); - returnVal = false; - } - } else { - addErrorMessage(initialErrorCount, "field '" + fieldNameLong + - "' cannot be added to the closed record '" + targetType + "'", errors); - returnVal = false; - } - } - if ((!returnVal) && ((errors == null) || (errors.size() >= MAX_TYPECAST_ERROR_COUNT + 1))) { - return false; - } - } - return returnVal; - } - - private static void addErrorMessage(int initialErrorCount, String errorMessage, List errors) { - if ((errors != null) && (errors.size() <= MAX_TYPECAST_ERROR_COUNT) && - ((errors.size() - initialErrorCount) == 0)) { - errors.add(errorMessage); - } - } - - private static boolean checkIsLikeTableType(Object sourceValue, BTableType targetType, - List unresolvedValues, boolean allowNumericConversion) { - if (!(sourceValue instanceof TableValueImpl)) { - return false; - } - TableValueImpl tableValue = (TableValueImpl) sourceValue; - BTableType sourceType = (BTableType) getImpliedType(tableValue.getType()); - if (targetType.getKeyType() != null && sourceType.getFieldNames().length == 0) { - return false; - } - - if (sourceType.getKeyType() != null && !checkIsType(tableValue.getKeyType(), targetType.getKeyType())) { - return false; - } - - TypeValuePair typeValuePair = new TypeValuePair(sourceValue, targetType); - if (unresolvedValues.contains(typeValuePair)) { - return true; - } - - Object[] objects = tableValue.values().toArray(); - for (Object object : objects) { - if (!checkIsLikeType(object, targetType.getConstrainedType(), allowNumericConversion)) { - return false; - } - } - return true; - } - - private static boolean checkFiniteTypeAssignable(Object sourceValue, Type sourceType, BFiniteType targetType, - List unresolvedValues, - boolean allowNumericConversion) { - if (targetType.valueSpace.size() == 1) { - Type valueType = getImpliedType(getType(targetType.valueSpace.iterator().next())); - if (!isSimpleBasicType(valueType) && valueType.getTag() != TypeTags.NULL_TAG) { - return checkIsLikeOnValue(null, sourceValue, sourceType, valueType, unresolvedValues, - allowNumericConversion, null); - } - } - - for (Object valueSpaceItem : targetType.valueSpace) { - // TODO: 8/13/19 Maryam fix for conversion - if (isFiniteTypeValue(sourceValue, sourceType, valueSpaceItem, allowNumericConversion)) { - return true; - } - } - return false; - } - - protected static boolean isFiniteTypeValue(Object sourceValue, Type sourceType, Object valueSpaceItem, - boolean allowNumericConversion) { - Type valueSpaceItemType = getType(valueSpaceItem); - int sourceTypeTag = getImpliedType(sourceType).getTag(); - int valueSpaceItemTypeTag = getImpliedType(valueSpaceItemType).getTag(); - if (valueSpaceItemTypeTag > TypeTags.DECIMAL_TAG) { - return valueSpaceItemTypeTag == sourceTypeTag && - (valueSpaceItem == sourceValue || valueSpaceItem.equals(sourceValue)); - } - - switch (sourceTypeTag) { - case TypeTags.BYTE_TAG: - case TypeTags.INT_TAG: - switch (valueSpaceItemTypeTag) { - case TypeTags.BYTE_TAG: - case TypeTags.INT_TAG: - return ((Number) sourceValue).longValue() == ((Number) valueSpaceItem).longValue(); - case TypeTags.FLOAT_TAG: - return ((Number) sourceValue).longValue() == ((Number) valueSpaceItem).longValue() && - allowNumericConversion; - case TypeTags.DECIMAL_TAG: - return ((Number) sourceValue).longValue() == ((DecimalValue) valueSpaceItem).intValue() && - allowNumericConversion; - } - case TypeTags.FLOAT_TAG: - switch (valueSpaceItemTypeTag) { - case TypeTags.BYTE_TAG: - case TypeTags.INT_TAG: - return ((Number) sourceValue).doubleValue() == ((Number) valueSpaceItem).doubleValue() - && allowNumericConversion; - case TypeTags.FLOAT_TAG: - return (((Number) sourceValue).doubleValue() == ((Number) valueSpaceItem).doubleValue() || - (Double.isNaN((Double) sourceValue) && Double.isNaN((Double) valueSpaceItem))); - case TypeTags.DECIMAL_TAG: - return ((Number) sourceValue).doubleValue() == ((DecimalValue) valueSpaceItem).floatValue() - && allowNumericConversion; - } - case TypeTags.DECIMAL_TAG: - switch (valueSpaceItemTypeTag) { - case TypeTags.BYTE_TAG: - case TypeTags.INT_TAG: - return checkDecimalEqual((DecimalValue) sourceValue, - DecimalValue.valueOf(((Number) valueSpaceItem).longValue())) && allowNumericConversion; - case TypeTags.FLOAT_TAG: - return checkDecimalEqual((DecimalValue) sourceValue, - DecimalValue.valueOf(((Number) valueSpaceItem).doubleValue())) && allowNumericConversion; - case TypeTags.DECIMAL_TAG: - return checkDecimalEqual((DecimalValue) sourceValue, (DecimalValue) valueSpaceItem); - } - default: - if (sourceTypeTag != valueSpaceItemTypeTag) { - return false; - } - return valueSpaceItem.equals(sourceValue); - } - } - - private static boolean checkIsErrorType(Type sourceType, BErrorType targetType, List unresolvedTypes) { - if (sourceType.getTag() != TypeTags.ERROR_TAG) { - return false; - } - // Handle recursive error types. - TypePair pair = new TypePair(sourceType, targetType); - if (unresolvedTypes.contains(pair)) { - return true; - } - unresolvedTypes.add(pair); - BErrorType bErrorType = (BErrorType) sourceType; - - if (!checkIsType(bErrorType.detailType, targetType.detailType, unresolvedTypes)) { - return false; - } - - if (targetType.typeIdSet == null) { - return true; - } - - BTypeIdSet sourceTypeIdSet = bErrorType.typeIdSet; - if (sourceTypeIdSet == null) { - return false; - } - - return sourceTypeIdSet.containsAll(targetType.typeIdSet); - } - - private static boolean checkIsLikeErrorType(Object sourceValue, BErrorType targetType, - List unresolvedValues, boolean allowNumericConversion) { - Type sourceTypeReferredType = getImpliedType(getType(sourceValue)); - if (sourceValue == null || sourceTypeReferredType.getTag() != TypeTags.ERROR_TAG) { - return false; - } - if (!checkIsLikeType(null, ((ErrorValue) sourceValue).getDetails(), targetType.detailType, - unresolvedValues, allowNumericConversion, null)) { - return false; - } - if (targetType.typeIdSet == null) { - return true; - } - BTypeIdSet sourceIdSet = ((BErrorType) sourceTypeReferredType).typeIdSet; - if (sourceIdSet == null) { - return false; - } - return sourceIdSet.containsAll(targetType.typeIdSet); - } - - static boolean isSimpleBasicType(Type type) { - return getImpliedType(type).getTag() < TypeTags.NULL_TAG; - } - /** * Deep value equality check for anydata. * @@ -3055,7 +880,7 @@ static boolean isStructuredType(Type type) { * * @since 0.995.0 */ - private static class TypePair { + static class TypePair { Type sourceType; Type targetType; @@ -3345,7 +1170,8 @@ private static BError createTypeCastError(Object value, Type targetType, List MAX_DISPLAYED_SOURCE_VALUE_LENGTH) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnyType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnyType.java index 5d3f5d12ec45..db30be644f9a 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnyType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnyType.java @@ -25,6 +25,7 @@ import io.ballerina.runtime.api.types.AnyType; import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.RefValue; import java.util.Optional; @@ -99,4 +100,9 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + SemType createSemType() { + return BTypeConverter.fromAnyType(this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BBooleanType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BBooleanType.java index 7eef2d162920..afcc60f6a844 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BBooleanType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BBooleanType.java @@ -18,15 +18,26 @@ package io.ballerina.runtime.internal.types; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.BooleanType; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.SemType; /** * {@code BBooleanType} represents boolean type in Ballerina. * * @since 0.995.0 */ -public class BBooleanType extends BType implements BooleanType { +public final class BBooleanType extends BSemTypeWrapper implements BooleanType { + + private static final BBooleanType TRUE = + new BBooleanType(new BBooleanTypeImpl(TypeConstants.BOOLEAN_TNAME, PredefinedTypes.EMPTY_MODULE), + Builder.booleanConst(true)); + private static final BBooleanType FALSE = + new BBooleanType(new BBooleanTypeImpl(TypeConstants.BOOLEAN_TNAME, PredefinedTypes.EMPTY_MODULE), + Builder.booleanConst(false)); /** * Create a {@code BBooleanType} which represents the boolean type. @@ -34,27 +45,42 @@ public class BBooleanType extends BType implements BooleanType { * @param typeName string name of the type */ public BBooleanType(String typeName, Module pkg) { - super(typeName, pkg, Boolean.class); + this(new BBooleanTypeImpl(typeName, pkg), Builder.booleanType()); } - @SuppressWarnings("unchecked") - public V getZeroValue() { - return (V) Boolean.FALSE; + public static BBooleanType singletonType(boolean value) { + return value ? TRUE : FALSE; } - @SuppressWarnings("unchecked") - @Override - public V getEmptyValue() { - return (V) Boolean.FALSE; + private BBooleanType(BBooleanTypeImpl bType, SemType semType) { + super(bType, semType); } - @Override - public int getTag() { - return TypeTags.BOOLEAN_TAG; - } + private static final class BBooleanTypeImpl extends BType implements BooleanType { + + private BBooleanTypeImpl(String typeName, Module pkg) { + super(typeName, pkg, Boolean.class); + } + + @SuppressWarnings("unchecked") + public V getZeroValue() { + return (V) Boolean.FALSE; + } + + @SuppressWarnings("unchecked") + @Override + public V getEmptyValue() { + return (V) Boolean.FALSE; + } + + @Override + public int getTag() { + return TypeTags.BOOLEAN_TAG; + } - @Override - public boolean isReadOnly() { - return true; + @Override + public boolean isReadOnly() { + return true; + } } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BByteType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BByteType.java index 8e5cda22dfbc..bb4fc8ae5045 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BByteType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BByteType.java @@ -20,14 +20,22 @@ import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.ByteType; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.PredefinedTypes.EMPTY_MODULE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED8_MAX_VALUE; /** * {@code BByteType} represents byte type in Ballerina. * * @since 0.995.0 */ -public class BByteType extends BType implements ByteType { +public final class BByteType extends BSemTypeWrapper implements ByteType { + + private static final BByteTypeImpl DEFAULT_B_TYPE = new BByteTypeImpl(TypeConstants.BYTE_TNAME, EMPTY_MODULE); /** * Create a {@code BByteType} which represents the byte type. @@ -35,28 +43,55 @@ public class BByteType extends BType implements ByteType { * @param typeName string name of the type */ public BByteType(String typeName, Module pkg) { - super(typeName, pkg, Integer.class); + this(new BByteTypeImpl(typeName, pkg), Builder.intRange(0, UNSIGNED8_MAX_VALUE)); } - @Override - @SuppressWarnings("unchecked") - public V getZeroValue() { - return (V) new Integer(0); + private BByteType(BByteTypeImpl bType, SemType semType) { + super(bType, semType); } - @Override - @SuppressWarnings("unchecked") - public V getEmptyValue() { - return (V) new Integer(0); + public static BByteType singletonType(long value) { + try { + return new BByteType((BByteTypeImpl) DEFAULT_B_TYPE.clone(), Builder.intConst(value)); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } } - @Override - public int getTag() { - return TypeTags.BYTE_TAG; - } + private static final class BByteTypeImpl extends BType implements ByteType, Cloneable { + + private BByteTypeImpl(String typeName, Module pkg) { + super(typeName, pkg, Integer.class); + } + + @Override + @SuppressWarnings("unchecked") + public V getZeroValue() { + return (V) new Integer(0); + } + + @Override + @SuppressWarnings("unchecked") + public V getEmptyValue() { + return (V) new Integer(0); + } + + @Override + public int getTag() { + return TypeTags.BYTE_TAG; + } + + @Override + public boolean isReadOnly() { + return true; + } - @Override - public boolean isReadOnly() { - return true; + @Override + protected Object clone() throws CloneNotSupportedException { + BType bType = (BType) super.clone(); + bType.setCachedImpliedType(null); + bType.setCachedReferredType(null); + return bType; + } } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BDecimalType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BDecimalType.java index a5af3b4d220a..ca66357278c6 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BDecimalType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BDecimalType.java @@ -20,18 +20,26 @@ import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.DecimalType; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.DecimalValue; import java.math.BigDecimal; +import static io.ballerina.runtime.api.PredefinedTypes.EMPTY_MODULE; + /** * {@code BDecimalType} represents decimal type in Ballerina. * This is a 128-bit decimal floating-point number according to the standard IEEE 754-2008 specifications. * * @since 0.995.0 */ -public class BDecimalType extends BType implements DecimalType { +public final class BDecimalType extends BSemTypeWrapper implements DecimalType { + + private static final BDecimalTypeImpl DEFAULT_B_TYPE = + new BDecimalTypeImpl(TypeConstants.DECIMAL_TNAME, EMPTY_MODULE); /** * Create a {@code BDecimalType} which represents the decimal type. @@ -39,28 +47,55 @@ public class BDecimalType extends BType implements DecimalType { * @param typeName string name of the type */ public BDecimalType(String typeName, Module pkg) { - super(typeName, pkg, DecimalValue.class); + this(new BDecimalTypeImpl(typeName, pkg), Builder.decimalType()); } - @Override - @SuppressWarnings("unchecked") - public V getZeroValue() { - return (V) new DecimalValue(BigDecimal.ZERO); + public static BDecimalType singletonType(BigDecimal value) { + try { + return new BDecimalType((BDecimalTypeImpl) DEFAULT_B_TYPE.clone(), Builder.decimalConst(value)); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } } - @Override - @SuppressWarnings("unchecked") - public V getEmptyValue() { - return (V) new DecimalValue(BigDecimal.ZERO); + private BDecimalType(BDecimalTypeImpl bType, SemType semType) { + super(bType, semType); } - @Override - public int getTag() { - return TypeTags.DECIMAL_TAG; - } + private static final class BDecimalTypeImpl extends BType implements DecimalType, Cloneable { + + private BDecimalTypeImpl(String typeName, Module pkg) { + super(typeName, pkg, DecimalValue.class); + } + + @Override + @SuppressWarnings("unchecked") + public V getZeroValue() { + return (V) new DecimalValue(BigDecimal.ZERO); + } + + @Override + @SuppressWarnings("unchecked") + public V getEmptyValue() { + return (V) new DecimalValue(BigDecimal.ZERO); + } + + @Override + public int getTag() { + return TypeTags.DECIMAL_TAG; + } + + @Override + public boolean isReadOnly() { + return true; + } - @Override - public boolean isReadOnly() { - return true; + @Override + protected Object clone() throws CloneNotSupportedException { + BType bType = (BType) super.clone(); + bType.setCachedImpliedType(null); + bType.setCachedReferredType(null); + return bType; + } } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFiniteType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFiniteType.java index 936f904164d8..25ae753eddca 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFiniteType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFiniteType.java @@ -21,6 +21,7 @@ import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.flags.TypeFlags; import io.ballerina.runtime.api.types.FiniteType; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.values.RefValue; @@ -57,6 +58,18 @@ public BFiniteType(String typeName, String originalName, Set values, int this.originalName = originalName; } + BFiniteType cloneWithValueSpace(Set valueSpace) { + BFiniteType newFiniteType = new BFiniteType(typeName, originalName, valueSpace, typeFlags); + newFiniteType.valueSpace = valueSpace; + newFiniteType.typeFlags = typeFlags; + newFiniteType.originalName = originalName; + + newFiniteType.typeName = typeName; + newFiniteType.pkg = pkg; + + return newFiniteType; + } + @Override public V getZeroValue() { if (valueSpace.stream().anyMatch(val -> val == null || TypeChecker.getType(val).isNilable())) { @@ -188,4 +201,9 @@ public boolean equals(Object o) { BFiniteType that = (BFiniteType) o; return this.valueSpace.size() == that.valueSpace.size() && this.valueSpace.containsAll(that.valueSpace); } + + @Override + SemType createSemType() { + return BTypeConverter.fromFiniteType(this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFloatType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFloatType.java index 2ef0d084d3e9..15de4aea83ab 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFloatType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFloatType.java @@ -19,7 +19,12 @@ import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.FloatType; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.PredefinedTypes.EMPTY_MODULE; /** * {@code BFloatType} represents a integer which is a 32-bit floating-point number according to the @@ -28,7 +33,7 @@ * @since 0.995.0 */ @SuppressWarnings("unchecked") -public class BFloatType extends BType implements FloatType { +public class BFloatType extends BSemTypeWrapper implements FloatType { /** * Create a {@code BFloatType} which represents the boolean type. @@ -36,26 +41,41 @@ public class BFloatType extends BType implements FloatType { * @param typeName string name of the type */ public BFloatType(String typeName, Module pkg) { - super(typeName, pkg, Double.class); + this(new BFloatTypeImpl(typeName, pkg), Builder.floatType()); } - @Override - public V getZeroValue() { - return (V) new Double(0); - } - - @Override - public V getEmptyValue() { - return (V) new Double(0); + private BFloatType(BFloatTypeImpl bType, SemType semType) { + super(bType, semType); } - @Override - public int getTag() { - return TypeTags.FLOAT_TAG; + public static BFloatType singletonType(Double value) { + return new BFloatType(new BFloatTypeImpl(TypeConstants.FLOAT_TNAME, EMPTY_MODULE), Builder.floatConst(value)); } - @Override - public boolean isReadOnly() { - return true; + private static final class BFloatTypeImpl extends BType implements FloatType { + + private BFloatTypeImpl(String typeName, Module pkg) { + super(typeName, pkg, Double.class); + } + + @Override + public V getZeroValue() { + return (V) new Double(0); + } + + @Override + public V getEmptyValue() { + return (V) new Double(0); + } + + @Override + public int getTag() { + return TypeTags.FLOAT_TAG; + } + + @Override + public boolean isReadOnly() { + return true; + } } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntegerType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntegerType.java index d8e290013da6..6a2db9bc2b78 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntegerType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntegerType.java @@ -1,25 +1,39 @@ /* -* Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. -* -* WSO2 Inc. 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. -*/ + * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 io.ballerina.runtime.internal.types; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.IntegerType; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED16_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED16_MIN_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED32_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED32_MIN_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED8_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED8_MIN_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED16_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED32_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED8_MAX_VALUE; /** * {@code BIntegerType} represents an integer which is a 32-bit signed number. @@ -27,9 +41,10 @@ * @since 0.995.0 */ @SuppressWarnings("unchecked") -public class BIntegerType extends BType implements IntegerType { +public final class BIntegerType extends BSemTypeWrapper implements IntegerType { - private final int tag; + private static final BIntegerTypeImpl DEFAULT_B_TYPE = + new BIntegerTypeImpl(TypeConstants.INT_TNAME, PredefinedTypes.EMPTY_MODULE, TypeTags.INT_TAG); /** * Create a {@code BIntegerType} which represents the boolean type. @@ -37,32 +52,93 @@ public class BIntegerType extends BType implements IntegerType { * @param typeName string name of the type */ public BIntegerType(String typeName, Module pkg) { - super(typeName, pkg, Long.class); - tag = TypeTags.INT_TAG; + this(new BIntegerTypeImpl(typeName, pkg, TypeTags.INT_TAG), Builder.intType()); } public BIntegerType(String typeName, Module pkg, int tag) { - super(typeName, pkg, Long.class); - this.tag = tag; + this(new BIntegerTypeImpl(typeName, pkg, tag), pickSemType(tag)); } - @Override - public V getZeroValue() { - return (V) new Long(0); + private BIntegerType(BIntegerTypeImpl bType, SemType semType) { + super(bType, semType); } - @Override - public V getEmptyValue() { - return (V) new Long(0); + private static SemType pickSemType(int tag) { + return switch (tag) { + case TypeTags.INT_TAG -> Builder.intType(); + case TypeTags.SIGNED8_INT_TAG -> Builder.intRange(SIGNED8_MIN_VALUE, SIGNED8_MAX_VALUE); + case TypeTags.SIGNED16_INT_TAG -> Builder.intRange(SIGNED16_MIN_VALUE, SIGNED16_MAX_VALUE); + case TypeTags.SIGNED32_INT_TAG -> Builder.intRange(SIGNED32_MIN_VALUE, SIGNED32_MAX_VALUE); + case TypeTags.UNSIGNED8_INT_TAG, TypeTags.BYTE_TAG -> Builder.intRange(0, UNSIGNED8_MAX_VALUE); + case TypeTags.UNSIGNED16_INT_TAG -> Builder.intRange(0, UNSIGNED16_MAX_VALUE); + case TypeTags.UNSIGNED32_INT_TAG -> Builder.intRange(0, UNSIGNED32_MAX_VALUE); + default -> throw new UnsupportedOperationException("Unexpected int tag"); + }; } - @Override - public int getTag() { - return tag; + public static BIntegerType singletonType(long value) { + if (value >= IntegerTypeCache.CACHE_MIN_VALUE && value <= IntegerTypeCache.CACHE_MAX_VALUE) { + return IntegerTypeCache.cache[(int) value - IntegerTypeCache.CACHE_MIN_VALUE]; + } + return createSingletonType(value); } - @Override - public boolean isReadOnly() { - return true; + private static BIntegerType createSingletonType(long value) { + try { + return new BIntegerType((BIntegerTypeImpl) DEFAULT_B_TYPE.clone(), Builder.intConst(value)); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + private static final class BIntegerTypeImpl extends BType implements IntegerType, Cloneable { + + private final int tag; + + private BIntegerTypeImpl(String typeName, Module pkg, int tag) { + super(typeName, pkg, Long.class); + this.tag = tag; + } + + @Override + public V getZeroValue() { + return (V) new Long(0); + } + + @Override + public V getEmptyValue() { + return (V) new Long(0); + } + + @Override + public int getTag() { + return tag; + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + BType bType = (BType) super.clone(); + bType.setCachedImpliedType(null); + bType.setCachedReferredType(null); + return bType; + } + } + + private static final class IntegerTypeCache { + + private static final BIntegerType[] cache; + private static final int CACHE_MAX_VALUE = 127; + private static final int CACHE_MIN_VALUE = -128; + static { + cache = new BIntegerType[CACHE_MAX_VALUE - CACHE_MIN_VALUE + 1]; + for (int i = CACHE_MIN_VALUE; i <= CACHE_MAX_VALUE; i++) { + cache[i - CACHE_MIN_VALUE] = createSingletonType(i); + } + } } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntersectionType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntersectionType.java index 58c22226890a..af2df12e4eab 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntersectionType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntersectionType.java @@ -24,6 +24,7 @@ import io.ballerina.runtime.api.types.IntersectableReferenceType; import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.SemType; import java.util.ArrayList; import java.util.Arrays; @@ -215,4 +216,13 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + SemType createSemType() { + Type effectiveType = getEffectiveType(); + if (effectiveType instanceof SemType semType) { + return semType; + } + return ((BType) effectiveType).createSemType(); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNeverType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNeverType.java index 9f0d4da9901b..2667619f6887 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNeverType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNeverType.java @@ -21,6 +21,7 @@ import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.NeverType; +import io.ballerina.runtime.api.types.semtype.Builder; /** * {@code BNeverType} represents the type of a {@code Never}. @@ -34,7 +35,7 @@ public class BNeverType extends BNullType implements NeverType { * @param pkg package path */ public BNeverType(Module pkg) { - super(TypeConstants.NEVER_TNAME, pkg); + super(TypeConstants.NEVER_TNAME, pkg, Builder.neverType()); } @Override diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNullType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNullType.java index 45e19ebea9c8..190948084305 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNullType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNullType.java @@ -20,13 +20,15 @@ import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.types.NullType; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.SemType; /** * {@code BNullType} represents the type of a {@code NullLiteral}. * * @since 0.995.0 */ -public class BNullType extends BType implements NullType { +public class BNullType extends BSemTypeWrapper implements NullType { /** * Create a {@code BNullType} represents the type of a {@code NullLiteral}. @@ -35,29 +37,49 @@ public class BNullType extends BType implements NullType { * @param pkg package path */ public BNullType(String typeName, Module pkg) { - super(typeName, pkg, null); + this(new BNullTypeImpl(typeName, pkg), Builder.nilType()); } - public V getZeroValue() { - return null; + BNullType(String typeName, Module pkg, SemType semType) { + this(new BNullTypeImpl(typeName, pkg), semType); } - @Override - public V getEmptyValue() { - return null; + private BNullType(BNullTypeImpl bNullType, SemType semType) { + super(bNullType, semType); } - @Override - public int getTag() { - return TypeTags.NULL_TAG; - } + private static final class BNullTypeImpl extends BType implements NullType { - public boolean isNilable() { - return true; - } + private BNullTypeImpl(String typeName, Module pkg) { + super(typeName, pkg, null); + } + + public V getZeroValue() { + return null; + } + + @Override + public V getEmptyValue() { + return null; + } + + @Override + public int getTag() { + return TypeTags.NULL_TAG; + } + + public boolean isNilable() { + return true; + } + + @Override + public boolean isReadOnly() { + return true; + } - @Override - public boolean isReadOnly() { - return true; + @Override + SemType createSemType() { + return Builder.nilType(); + } } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BReadonlyType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BReadonlyType.java index 856da35eaa16..cfcdfe575b87 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BReadonlyType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BReadonlyType.java @@ -20,6 +20,7 @@ import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.types.ReadonlyType; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.RefValue; /** @@ -56,4 +57,9 @@ public boolean isNilable() { public boolean isReadOnly() { return true; } + + @Override + SemType createSemType() { + return BTypeConverter.fromReadonly(this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BRecordType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BRecordType.java index aa357855dae5..14e6d0d9a63d 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BRecordType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BRecordType.java @@ -28,6 +28,7 @@ import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BFunctionPointer; import io.ballerina.runtime.api.values.BMap; @@ -218,4 +219,8 @@ public void setDefaultValue(String fieldName, BFunctionPointer defaul return defaultValues; } + @Override + SemType createSemType() { + return BTypeConverter.fromRecordType(this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeWrapper.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeWrapper.java new file mode 100644 index 000000000000..29fbc4bb134a --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeWrapper.java @@ -0,0 +1,163 @@ +/* + * 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. + */ + +package io.ballerina.runtime.internal.types; + +import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.types.IntersectionType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.SemType; + +/** + * Decorator on {@code BTypes} allowing them to behave as {@code SemType}. All {@code Types} that needs to behave as + * both a {@code BType} and a {@code SemType} should extend this class. + * + * @since 2201.10.0 + */ +public non-sealed class BSemTypeWrapper extends SemType implements Type { + + private final BType bType; + protected final String typeName; // Debugger uses this field to show the type name + + BSemTypeWrapper(BType bType, SemType semType) { + super(semType); + this.bType = bType; + this.typeName = bType.typeName; + } + + public Class getValueClass() { + return bType.getValueClass(); + } + + public V getZeroValue() { + return bType.getZeroValue(); + } + + @Override + public V getEmptyValue() { + return bType.getEmptyValue(); + } + + @Override + public int getTag() { + return bType.getTag(); + } + + @Override + public String toString() { + return bType.toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BSemTypeWrapper other)) { + return false; + } + return bType.equals(other.bType); + } + + @Override + public boolean isNilable() { + return bType.isNilable(); + } + + @Override + public int hashCode() { + return bType.hashCode(); + } + + @Override + public String getName() { + return bType.getName(); + } + + @Override + public String getQualifiedName() { + return bType.getQualifiedName(); + } + + @Override + public Module getPackage() { + return bType.getPackage(); + } + + @Override + public boolean isPublic() { + return bType.isPublic(); + } + + @Override + public boolean isNative() { + return bType.isNative(); + } + + @Override + public boolean isAnydata() { + return bType.isAnydata(); + } + + @Override + public boolean isPureType() { + return bType.isPureType(); + } + + @Override + public boolean isReadOnly() { + return bType.isReadOnly(); + } + + @Override + public Type getImmutableType() { + return bType.getImmutableType(); + } + + @Override + public void setImmutableType(IntersectionType immutableType) { + bType.setImmutableType(immutableType); + } + + @Override + public Module getPkg() { + return bType.getPkg(); + } + + @Override + public long getFlags() { + return bType.getFlags(); + } + + @Override + public void setCachedReferredType(Type type) { + bType.setCachedReferredType(type); + } + + @Override + public Type getCachedReferredType() { + return bType.getCachedReferredType(); + } + + @Override + public void setCachedImpliedType(Type type) { + bType.setCachedImpliedType(type); + } + + @Override + public Type getCachedImpliedType() { + return bType.getCachedImpliedType(); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStringType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStringType.java index 359beacd3d21..9825656ce9ee 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStringType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStringType.java @@ -1,26 +1,29 @@ /* -* Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. -* -* WSO2 Inc. 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. -*/ + * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 io.ballerina.runtime.internal.types; import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.constants.RuntimeConstants; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.StringType; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.SemType; /** * {@code BStringType} represents a String type in ballerina. @@ -28,9 +31,12 @@ * @since 0.995.0 */ @SuppressWarnings("unchecked") -public class BStringType extends BType implements StringType { +public final class BStringType extends BSemTypeWrapper implements StringType { - private final int tag; + // We are creating separate empty module instead of reusing PredefinedTypes.EMPTY_MODULE to avoid cyclic + // dependencies. + private static final BStringTypeImpl DEFAULT_B_TYPE = + new BStringTypeImpl(TypeConstants.STRING_TNAME, new Module(null, null, null), TypeTags.STRING_TAG); /** * Create a {@code BStringType} which represents the boolean type. @@ -38,31 +44,67 @@ public class BStringType extends BType implements StringType { * @param typeName string name of the type */ public BStringType(String typeName, Module pkg) { - super(typeName, pkg, String.class); - tag = TypeTags.STRING_TAG; + this(new BStringTypeImpl(typeName, pkg, TypeTags.STRING_TAG), Builder.stringType()); } public BStringType(String typeName, Module pkg, int tag) { - super(typeName, pkg, String.class); - this.tag = tag; + this(new BStringTypeImpl(typeName, pkg, tag), pickSemtype(tag)); } - public V getZeroValue() { - return (V) RuntimeConstants.STRING_EMPTY_VALUE; + private BStringType(BStringTypeImpl bType, SemType semType) { + super(bType, semType); } - @Override - public V getEmptyValue() { - return (V) RuntimeConstants.STRING_EMPTY_VALUE; + public static BStringType singletonType(String value) { + try { + return new BStringType((BStringTypeImpl) DEFAULT_B_TYPE.clone(), Builder.stringConst(value)); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } } - @Override - public int getTag() { - return tag; + private static SemType pickSemtype(int tag) { + return switch (tag) { + case TypeTags.STRING_TAG -> Builder.stringType(); + case TypeTags.CHAR_STRING_TAG -> Builder.charType(); + default -> throw new IllegalStateException("Unexpected string type tag: " + tag); + }; } - @Override - public boolean isReadOnly() { - return true; + private static final class BStringTypeImpl extends BType implements StringType, Cloneable { + + private final int tag; + + private BStringTypeImpl(String typeName, Module pkg, int tag) { + super(typeName, pkg, String.class); + this.tag = tag; + } + + public V getZeroValue() { + return (V) RuntimeConstants.STRING_EMPTY_VALUE; + } + + @Override + public V getEmptyValue() { + return (V) RuntimeConstants.STRING_EMPTY_VALUE; + } + + @Override + public int getTag() { + return tag; + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + BType bType = (BType) super.clone(); + bType.setCachedImpliedType(null); + bType.setCachedReferredType(null); + return bType; + } } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStructureType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStructureType.java index 58b453e9f082..4577b113ecd6 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStructureType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStructureType.java @@ -70,6 +70,7 @@ public Map getFields() { public void setFields(Map fields) { this.fields = fields; + resetSemTypeCache(); } public long getFlags() { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTupleType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTupleType.java index 4e69b2e41dab..44f0cd00325d 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTupleType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTupleType.java @@ -24,6 +24,7 @@ import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.TupleType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.ReadOnlyUtils; import io.ballerina.runtime.internal.values.TupleValueImpl; @@ -297,4 +298,9 @@ public void setIntersectionType(IntersectionType intersectionType) { public String getAnnotationKey() { return Utils.decodeIdentifier(this.typeName); } + + @Override + SemType createSemType() { + return BTypeConverter.fromTupleType(this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java index 866432570c59..a2f915cd2ee9 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java @@ -22,10 +22,13 @@ import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.SubTypeData; import java.util.Objects; +import java.util.function.Supplier; /** * {@code BType} represents a type in Ballerina. @@ -37,13 +40,14 @@ * * @since 0.995.0 */ -public abstract class BType implements Type { +public abstract class BType implements Type, SubTypeData, Supplier { protected String typeName; protected Module pkg; protected Class valueClass; private int hashCode; private Type cachedReferredType = null; private Type cachedImpliedType = null; + SemType cachedSemType = null; protected BType(String typeName, Module pkg, Class valueClass) { this.typeName = typeName; @@ -212,4 +216,22 @@ public void setCachedImpliedType(Type type) { public Type getCachedImpliedType() { return this.cachedImpliedType; } + + // If any child class allow mutation that will affect the SemType, it must call this method. + final void resetSemTypeCache() { + cachedSemType = null; + } + + // If any child class partially implement SemType it must override this method. + SemType createSemType() { + return BTypeConverter.wrapAsPureBType(this); + } + + @Override + public final SemType get() { + if (cachedSemType == null) { + cachedSemType = createSemType(); + } + return cachedSemType; + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeConverter.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeConverter.java new file mode 100644 index 000000000000..a48604eeddb7 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeConverter.java @@ -0,0 +1,193 @@ +/* + * 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. + */ + +package io.ballerina.runtime.internal.types; + +import io.ballerina.runtime.api.flags.SymbolFlags; +import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.internal.types.semtype.BSubType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * This is a utility class for {@code Builder} class so that BTypes don't need to expose their internal structure as + * public to create semtypes from them. + * + * @since 2201.10.0 + */ +final class BTypeConverter { + + private BTypeConverter() { + } + + private static final SemType READONLY_SEMTYPE_PART = + unionOf(Builder.stringType(), Builder.booleanType(), Builder.intType(), Builder.floatType(), + Builder.nilType(), Builder.decimalType()); + private static final SemType ANY_SEMTYPE_PART = + unionOf(Builder.stringType(), Builder.booleanType(), Builder.intType(), Builder.floatType(), + Builder.nilType(), Builder.decimalType()); + + private static SemType unionOf(SemType... semTypes) { + SemType result = Builder.neverType(); + for (SemType semType : semTypes) { + result = Core.union(result, semType); + } + return result; + } + + private static SemType from(Type type) { + if (type instanceof SemType semType) { + return semType; + } else if (type instanceof BType bType) { + return fromBType(bType); + } + throw new IllegalArgumentException("Unsupported type: " + type); + } + + private static SemType fromBType(BType innerType) { + return innerType.get(); + } + + static SemType fromReadonly(BReadonlyType readonlyType) { + SemType bTypePart = wrapAsPureBType(readonlyType); + return Core.union(READONLY_SEMTYPE_PART, bTypePart); + } + + static SemType fromTupleType(BTupleType tupleType) { + for (Type type : tupleType.getTupleTypes()) { + if (Core.isNever(from(type))) { + return Builder.neverType(); + } + } + return wrapAsPureBType(tupleType); + } + + static SemType wrapAsPureBType(BType tupleType) { + return Builder.basicSubType(BasicTypeCode.BT_B_TYPE, BSubType.wrap(tupleType)); + } + + static SemType fromAnyType(BAnyType anyType) { + SemType bTypePart = wrapAsPureBType(anyType); + return Core.union(ANY_SEMTYPE_PART, bTypePart); + } + + static SemType fromRecordType(BRecordType recordType) { + for (Field field : recordType.fields.values()) { + if (!SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL)) { + SemType fieldType = from(field.getFieldType()); + if (Core.isNever(fieldType)) { + return Builder.neverType(); + } + } + } + return wrapAsPureBType(recordType); + } + + static SemType fromFiniteType(BFiniteType finiteType) { + BTypeParts parts = splitFiniteType(finiteType); + if (parts.bTypeParts().isEmpty()) { + return parts.semTypePart(); + } + BType newFiniteType = (BType) parts.bTypeParts().get(0); + SemType bTypePart = wrapAsPureBType(newFiniteType); + return Core.union(parts.semTypePart(), bTypePart); + } + + static SemType fromUnionType(BUnionType unionType) { + BTypeParts parts = splitUnion(unionType); + if (parts.bTypeParts().isEmpty()) { + return parts.semTypePart(); + } + SemType bTypePart = Builder.basicSubType(BasicTypeCode.BT_B_TYPE, BSubType.wrap(unionType)); + return Core.union(parts.semTypePart(), bTypePart); + } + + private record BTypeParts(SemType semTypePart, List bTypeParts) { + + } + + private static BTypeParts split(Type type) { + if (type instanceof SemType) { + return new BTypeParts(from(type), Collections.emptyList()); + } else if (type instanceof BUnionType unionType) { + return splitUnion(unionType); + } else if (type instanceof BAnyType anyType) { + return splitAnyType(anyType); + } else if (type instanceof BTypeReferenceType referenceType) { + return split(referenceType.getReferredType()); + } else if (type instanceof BIntersectionType intersectionType) { + return split(intersectionType.getEffectiveType()); + } else if (type instanceof BReadonlyType readonlyType) { + return splitReadonly(readonlyType); + } else if (type instanceof BFiniteType finiteType) { + return splitFiniteType(finiteType); + } else { + return new BTypeParts(Builder.neverType(), List.of(type)); + } + } + + private static BTypeParts splitAnyType(BAnyType anyType) { + return new BTypeParts(ANY_SEMTYPE_PART, List.of(anyType)); + } + + private static BTypeParts splitFiniteType(BFiniteType finiteType) { + Set newValueSpace = new HashSet<>(finiteType.valueSpace.size()); + SemType semTypePart = Builder.neverType(); + for (var each : finiteType.valueSpace) { + // TODO: lift this to Builder (Object) -> Type + Optional semType = Builder.typeOf(each); + if (semType.isPresent()) { + semTypePart = Core.union(semTypePart, semType.get()); + } else { + newValueSpace.add(each); + } + } + if (newValueSpace.isEmpty()) { + return new BTypeParts(semTypePart, List.of()); + } + BFiniteType newFiniteType = finiteType.cloneWithValueSpace(newValueSpace); + return new BTypeParts(semTypePart, List.of(newFiniteType)); + } + + private static BTypeParts splitReadonly(BReadonlyType readonlyType) { + // TODO: this is not exactly correct + return new BTypeParts(READONLY_SEMTYPE_PART, List.of(readonlyType)); + } + + private static BTypeParts splitUnion(BUnionType unionType) { + List members = Collections.unmodifiableList(unionType.getMemberTypes()); + List bTypeMembers = new ArrayList<>(members.size()); + SemType semTypePart = Builder.neverType(); + for (Type member : members) { + BTypeParts memberParts = split(member); + semTypePart = Core.union(memberParts.semTypePart(), semTypePart); + bTypeMembers.addAll(memberParts.bTypeParts()); + } + return new BTypeParts(semTypePart, Collections.unmodifiableList(bTypeMembers)); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeReferenceType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeReferenceType.java index f43cd540dc0e..9292d0c056e4 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeReferenceType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeReferenceType.java @@ -25,6 +25,7 @@ import io.ballerina.runtime.api.types.IntersectableReferenceType; import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.SemType; import java.util.Objects; import java.util.Optional; @@ -126,4 +127,13 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + SemType createSemType() { + Type referredType = getReferredType(); + if (referredType instanceof SemType semType) { + return semType; + } + return ((BType) referredType).createSemType(); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BUnionType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BUnionType.java index 527ce7d0f651..6e734b1cb4e1 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BUnionType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BUnionType.java @@ -25,6 +25,7 @@ import io.ballerina.runtime.api.types.SelectivelyImmutableReferenceType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.values.ReadOnlyUtils; @@ -167,6 +168,7 @@ public void setMemberTypes(Type[] members) { } this.memberTypes = readonly ? getReadOnlyTypes(members) : Arrays.asList(members); setFlagsBasedOnMembers(); + resetSemTypeCache(); } public void setOriginalMemberTypes(Type[] originalMemberTypes) { @@ -543,4 +545,9 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + SemType createSemType() { + return BTypeConverter.fromUnionType(this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/AllOrNothing.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/AllOrNothing.java new file mode 100644 index 000000000000..d7e95aa12494 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/AllOrNothing.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * 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 io.ballerina.runtime.internal.types.semtype; + +/** + * Represent cases where a subtype is either all or nothing of the basic type. For example if StringSubType has All as + * it's subtype data that means subtype is actually String basic type and nothing means it doesn't have any string + * subtype + * + * @since 2201.10.0 + */ +public enum AllOrNothing implements SubTypeData { + ALL, + NOTHING +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BBooleanSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BBooleanSubType.java new file mode 100644 index 000000000000..9252938a17e8 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BBooleanSubType.java @@ -0,0 +1,156 @@ +/* + * 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. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SubType; + +/** + * Runtime representation of BooleanSubType. + * + * @since 2201.10.0 + */ +public final class BBooleanSubType extends SubType { + + private final BBooleanSubTypeData data; + private static final BBooleanSubType ALL = new BBooleanSubType(BBooleanSubTypeData.ALL); + private static final BBooleanSubType NOTHING = new BBooleanSubType(BBooleanSubTypeData.NOTHING); + private static final BBooleanSubType TRUE = new BBooleanSubType(BBooleanSubTypeData.TRUE); + private static final BBooleanSubType FALSE = new BBooleanSubType(BBooleanSubTypeData.FALSE); + + private BBooleanSubType(BBooleanSubTypeData data) { + super(data.isAll(), data.isNothing()); + this.data = data; + } + + public static BBooleanSubType from(boolean value) { + return value ? TRUE : FALSE; + } + + @Override + public SubType union(SubType otherSubtype) { + if (!(otherSubtype instanceof BBooleanSubType other)) { + throw new IllegalArgumentException("union of different subtypes"); + } + if (this.isAll()) { + return this; + } + if (other.isAll()) { + return other; + } + if (this.isNothing()) { + return other; + } + if (other.isNothing()) { + return this; + } + if (this.data.value == other.data.value) { + return this; + } + return ALL; + } + + @Override + public SubType intersect(SubType otherSubtype) { + if (!(otherSubtype instanceof BBooleanSubType other)) { + throw new IllegalArgumentException("intersection of different subtypes"); + } + if (this.isAll()) { + return other; + } + if (other.isAll()) { + return this; + } + if (this.isNothing() || other.isNothing()) { + return NOTHING; + } + if (this.data.value == other.data.value) { + return this; + } + return NOTHING; + } + + @Override + public SubType diff(SubType otherSubtype) { + if (!(otherSubtype instanceof BBooleanSubType other)) { + throw new IllegalArgumentException("diff of different subtypes"); + } + if (this.isAll() && other.isAll()) { + return NOTHING; + } + if (this.isNothing() || other.isAll()) { + return NOTHING; + } + if (other.isNothing()) { + return this; + } + if (this.isAll()) { + if (other.isNothing()) { + return this; + } + return from(!other.data.value); + } + return this.data.value == other.data.value ? NOTHING : this; + } + + @Override + public SubType complement() { + if (isAll()) { + return NOTHING; + } + if (isNothing()) { + return ALL; + } + return from(!data.value); + } + + @Override + public boolean isEmpty() { + return data.isNothing(); + } + + @Override + public SubTypeData data() { + return data.toData(); + } + + private record BBooleanSubTypeData(boolean isAll, boolean isNothing, boolean value) { + + private static final BBooleanSubTypeData ALL = new BBooleanSubTypeData(true, false, false); + private static final BBooleanSubTypeData NOTHING = new BBooleanSubTypeData(false, true, false); + private static final BBooleanSubTypeData TRUE = new BBooleanSubTypeData(false, false, true); + private static final BBooleanSubTypeData FALSE = new BBooleanSubTypeData(false, false, false); + + static BBooleanSubTypeData from(boolean value) { + return value ? TRUE : FALSE; + } + + SubTypeData toData() { + if (isAll()) { + return AllOrNothing.ALL; + } else if (isNothing()) { + return AllOrNothing.NOTHING; + } + return new BooleanSubTypeData(value()); + } + } + + private record BooleanSubTypeData(boolean value) implements SubTypeData { + + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BDecimalSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BDecimalSubType.java new file mode 100644 index 000000000000..6bdbd4839039 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BDecimalSubType.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * 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 io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Runtime representation of DecimalSubType. + * + * @since 2201.10.0 + */ +public final class BDecimalSubType extends SubType { + + final SubTypeData data; + + private BDecimalSubType(SubTypeData data) { + super(data == AllOrNothing.ALL, data == AllOrNothing.NOTHING); + this.data = data; + } + + private static final BDecimalSubType ALL = new BDecimalSubType(AllOrNothing.ALL); + private static final BDecimalSubType NOTHING = new BDecimalSubType(AllOrNothing.NOTHING); + + public static BDecimalSubType createDecimalSubType(boolean allowed, BigDecimal[] values) { + if (values.length == 0) { + if (!allowed) { + return ALL; + } else { + return NOTHING; + } + } + Arrays.sort(values); + return new BDecimalSubType(new DecimalSubTypeData(allowed, values)); + } + + @Override + public SubType union(SubType otherSubtype) { + BDecimalSubType other = (BDecimalSubType) otherSubtype; + if (data instanceof AllOrNothing) { + if (data == AllOrNothing.ALL) { + return this; + } else { + return other; + } + } else if (other.data instanceof AllOrNothing) { + if (other.data == AllOrNothing.ALL) { + return other; + } else { + return this; + } + } + List values = new ArrayList<>(); + DecimalSubTypeData data = (DecimalSubTypeData) this.data; + DecimalSubTypeData otherData = (DecimalSubTypeData) other.data; + boolean allowed = data.union(otherData, values); + return createDecimalSubType(allowed, values.toArray(BigDecimal[]::new)); + } + + @Override + public SubType intersect(SubType otherSubtype) { + BDecimalSubType other = (BDecimalSubType) otherSubtype; + if (data instanceof AllOrNothing) { + if (data == AllOrNothing.ALL) { + return other; + } else { + return NOTHING; + } + } else if (other.data instanceof AllOrNothing) { + if (other.data == AllOrNothing.ALL) { + return this; + } else { + return NOTHING; + } + } + List values = new ArrayList<>(); + DecimalSubTypeData data = (DecimalSubTypeData) this.data; + DecimalSubTypeData otherData = (DecimalSubTypeData) other.data; + boolean allowed = data.intersect(otherData, values); + return createDecimalSubType(allowed, values.toArray(BigDecimal[]::new)); + } + + @Override + public SubType complement() { + if (data == AllOrNothing.ALL) { + return NOTHING; + } else if (data == AllOrNothing.NOTHING) { + return ALL; + } + DecimalSubTypeData data = (DecimalSubTypeData) this.data; + return createDecimalSubType(!data.allowed, data.values); + } + + @Override + public boolean isEmpty() { + return data == AllOrNothing.NOTHING; + } + + @Override + public SubTypeData data() { + return data; + } + + public BigDecimal defaultValue() { + if (data instanceof DecimalSubTypeData subTypeData && subTypeData.allowed && subTypeData.values.length == 1) { + return subTypeData.values[0]; + } + return null; + } + + @Override + public String toString() { + if (data instanceof DecimalSubTypeData subTypeData && subTypeData.allowed) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < subTypeData.values.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(subTypeData.values[i]); + } + return sb.toString(); + } + return "decimal"; + } + + private static final class DecimalSubTypeData extends EnumerableSubtypeData implements SubTypeData { + + private final boolean allowed; + private final BigDecimal[] values; + + private DecimalSubTypeData(boolean allowed, BigDecimal[] values) { + this.allowed = allowed; + this.values = values; + } + + @Override + boolean allowed() { + return allowed; + } + + @Override + BigDecimal[] values() { + return values; + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BFloatSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BFloatSubType.java new file mode 100644 index 000000000000..cb1d63a2f0a3 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BFloatSubType.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * 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 io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Runtime representation of FloatSubType. + * + * @since 2201.10.0 + */ +public final class BFloatSubType extends SubType { + + final SubTypeData data; + + private BFloatSubType(SubTypeData data) { + super(data == AllOrNothing.ALL, data == AllOrNothing.NOTHING); + this.data = data; + } + + private static final BFloatSubType ALL = new BFloatSubType(AllOrNothing.ALL); + private static final BFloatSubType NOTHING = new BFloatSubType(AllOrNothing.NOTHING); + + public static BFloatSubType createFloatSubType(boolean allowed, Double[] values) { + if (values.length == 0) { + if (!allowed) { + return ALL; + } else { + return NOTHING; + } + } + return new BFloatSubType(new FloatSubTypeData(allowed, values)); + } + + @Override + public SubType union(SubType otherSubtype) { + BFloatSubType other = (BFloatSubType) otherSubtype; + if (data instanceof AllOrNothing) { + if (data == AllOrNothing.ALL) { + return this; + } else { + return other; + } + } else if (other.data instanceof AllOrNothing) { + if (other.data == AllOrNothing.ALL) { + return other; + } else { + return this; + } + } + List values = new ArrayList<>(); + FloatSubTypeData data = (FloatSubTypeData) this.data; + FloatSubTypeData otherData = (FloatSubTypeData) other.data; + boolean allowed = data.union(otherData, values); + return createFloatSubType(allowed, values.toArray(Double[]::new)); + } + + @Override + public SubType intersect(SubType otherSubtype) { + BFloatSubType other = (BFloatSubType) otherSubtype; + if (data instanceof AllOrNothing) { + if (data == AllOrNothing.ALL) { + return other; + } else { + return NOTHING; + } + } else if (other.data instanceof AllOrNothing) { + if (other.data == AllOrNothing.ALL) { + return this; + } else { + return NOTHING; + } + } + List values = new ArrayList<>(); + FloatSubTypeData data = (FloatSubTypeData) this.data; + FloatSubTypeData otherData = (FloatSubTypeData) other.data; + boolean allowed = data.intersect(otherData, values); + return createFloatSubType(allowed, values.toArray(Double[]::new)); + } + + @Override + public SubType complement() { + if (data == AllOrNothing.ALL) { + return NOTHING; + } else if (data == AllOrNothing.NOTHING) { + return ALL; + } + FloatSubTypeData data = (FloatSubTypeData) this.data; + return createFloatSubType(!data.allowed, data.values); + } + + @Override + public boolean isEmpty() { + return data == AllOrNothing.NOTHING; + } + + @Override + public SubTypeData data() { + return data; + } + + static final class FloatSubTypeData extends EnumerableSubtypeData implements SubTypeData { + + private final boolean allowed; + private final Double[] values; + + private FloatSubTypeData(boolean allowed, Double[] values) { + this.allowed = allowed; + this.values = filteredValues(values); + } + + private static Double[] filteredValues(Double[] values) { + for (int i = 0; i < values.length; i++) { + values[i] = canon(values[i]); + } + if (values.length < 2) { + return values; + } + Arrays.sort(values); + Double[] buffer = new Double[values.length]; + buffer[0] = values[0]; + int bufferLen = 1; + for (int i = 1; i < values.length; i++) { + Double value = values[i]; + Double prevValue = values[i - 1]; + if (isSame(value, prevValue)) { + continue; + } + buffer[bufferLen++] = value; + } + return Arrays.copyOf(buffer, bufferLen); + } + + private static Double canon(Double d) { + if (d.equals(0.0) || d.equals(-0.0)) { + return 0.0; + } + return d; + } + + private static boolean isSame(double f1, double f2) { + if (Double.isNaN(f1)) { + return Double.isNaN(f2); + } + return f1 == f2; + } + + @Override + boolean allowed() { + return allowed; + } + + @Override + Double[] values() { + return values; + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BIntSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BIntSubType.java new file mode 100644 index 000000000000..429751c6f244 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BIntSubType.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * 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 io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static io.ballerina.runtime.api.constants.RuntimeConstants.INT_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.INT_MIN_VALUE; + +/** + * Runtime representation of IntSubType. + * + * @since 2201.10.0 + */ +public final class BIntSubType extends SubType { + + final SubTypeData data; + + private BIntSubType(SubTypeData data) { + super(data == AllOrNothing.ALL, data == AllOrNothing.NOTHING); + this.data = data; + } + + private static final BIntSubType ALL = new BIntSubType(AllOrNothing.ALL); + private static final BIntSubType NOTHING = new BIntSubType(AllOrNothing.NOTHING); + + public static BIntSubType createIntSubType(List values) { + Collections.sort(values); + List ranges = new ArrayList<>(); + long start = values.get(0); + long end = start; + for (int i = 1; i < values.size(); i++) { + long value = values.get(i); + if (value == end + 1) { + end = value; + } else { + ranges.add(new Range(start, end)); + start = value; + end = value; + } + } + ranges.add(new Range(start, end)); + return new BIntSubType(new IntSubTypeData(ranges.toArray(Range[]::new))); + } + + public static BIntSubType createIntSubType(long min, long max) { + Range range = new Range(min, max); + Range[] ranges = {range}; + return new BIntSubType(new IntSubTypeData(ranges)); + } + + @Override + public SubType union(SubType otherSubType) { + BIntSubType other = (BIntSubType) otherSubType; + if (data instanceof AllOrNothing) { + if (data == AllOrNothing.ALL) { + return this; + } else { + return other; + } + } else if (other.data instanceof AllOrNothing) { + if (other.data == AllOrNothing.ALL) { + return other; + } else { + return this; + } + } + IntSubTypeData thisData = (IntSubTypeData) data; + IntSubTypeData otherData = (IntSubTypeData) other.data; + IntSubTypeData v = thisData.union(otherData); + Range[] resultRanges = v.ranges; + if (resultRanges.length == 1 && resultRanges[0].min == INT_MAX_VALUE && resultRanges[0].max == INT_MAX_VALUE) { + return ALL; + } + return new BIntSubType(v); + } + + @Override + public SubType intersect(SubType otherSubType) { + BIntSubType other = (BIntSubType) otherSubType; + if (data instanceof AllOrNothing) { + if (data == AllOrNothing.ALL) { + return other; + } else { + return NOTHING; + } + } else if (other.data instanceof AllOrNothing) { + if (other.data == AllOrNothing.ALL) { + return this; + } else { + return NOTHING; + } + } + IntSubTypeData thisData = (IntSubTypeData) data; + IntSubTypeData otherData = (IntSubTypeData) other.data; + IntSubTypeData v = thisData.intersect(otherData); + Range[] resultRanges = v.ranges; + if (resultRanges.length == 0) { + return NOTHING; + } + return new BIntSubType(v); + } + + @Override + public SubType complement() { + if (this.data == AllOrNothing.ALL) { + return NOTHING; + } else if (this.data == AllOrNothing.NOTHING) { + return ALL; + } + IntSubTypeData intData = (IntSubTypeData) data; + return new BIntSubType(intData.complement()); + } + + @Override + public boolean isEmpty() { + return data == AllOrNothing.NOTHING; + } + + @Override + public SubTypeData data() { + return data; + } + + record Range(long min, long max) { + + } + + static final class IntSubTypeData implements SubTypeData { + + private final Range[] ranges; + + private IntSubTypeData(Range[] ranges) { + this.ranges = ranges; + } + + private IntSubTypeData union(IntSubTypeData other) { + List result = new ArrayList<>(); + int i1 = 0; + int i2 = 0; + Range[] v1 = this.ranges; + Range[] v2 = other.ranges; + int len1 = ranges.length; + int len2 = other.ranges.length; + while (true) { + if (i1 >= len1) { + if (i2 >= len2) { + break; + } + rangeUnionPush(result, v2[i2]); + i2++; + } else if (i2 >= len2) { + rangeUnionPush(result, v1[i1]); + i1++; + } else { + Range r1 = v1[i1]; + Range r2 = v2[i2]; + RangeOpResult combined = rangeUnion(r1, r2); + switch (combined.tag) { + case OVERLAP -> { + rangeUnionPush(result, combined.range); + i1++; + i2++; + } + case BEFORE -> { + rangeUnionPush(result, r1); + i1++; + } + case AFTER -> { + rangeUnionPush(result, r2); + i2++; + } + } + } + } + return new IntSubTypeData(result.toArray(Range[]::new)); + } + + IntSubTypeData intersect(IntSubTypeData other) { + List result = new ArrayList<>(); + int i1 = 0; + int i2 = 0; + Range[] v1 = this.ranges; + Range[] v2 = other.ranges; + int len1 = ranges.length; + int len2 = other.ranges.length; + while (true) { + if (i1 >= len1 || i2 >= len2) { + break; + } + Range r1 = v1[i1]; + Range r2 = v2[i2]; + RangeOpResult combined = rangeIntersect(r1, r2); + switch (combined.tag) { + case OVERLAP -> { + rangeUnionPush(result, combined.range); + i1++; + i2++; + } + case BEFORE -> i1++; + case AFTER -> i2++; + } + } + return new IntSubTypeData(result.toArray(Range[]::new)); + } + + IntSubTypeData complement() { + List result = new ArrayList<>(); + Range[] v = this.ranges; + int len = v.length; + long min = v[0].min; + if (min > INT_MIN_VALUE) { + result.add(new Range(INT_MIN_VALUE, min - 1)); + } + for (int i = 1; i < len; i++) { + result.add(new Range(v[i - 1].max + 1, v[i].min - 1)); + } + long max = v[v.length - 1].max; + if (max < INT_MAX_VALUE) { + result.add(new Range(max + 1, INT_MAX_VALUE)); + } + return new IntSubTypeData(result.toArray(Range[]::new)); + } + + private static void rangeUnionPush(List ranges, Range next) { + int lastIndex = ranges.size() - 1; + if (lastIndex < 0) { + ranges.add(next); + return; + } + RangeOpResult result = rangeUnion(ranges.get(lastIndex), next); + if (result.tag == RangeOpResultTag.OVERLAP) { + ranges.set(lastIndex, result.range); + } else { + ranges.add(next); + } + } + + private static RangeOpResult rangeIntersect(Range r1, Range r2) { + if (r1.max < r2.min) { + return new RangeOpResult(RangeOpResultTag.BEFORE, null); + } + if (r2.max < r1.min) { + return new RangeOpResult(RangeOpResultTag.AFTER, null); + } + return new RangeOpResult(RangeOpResultTag.OVERLAP, + new Range(Math.max(r1.min, r2.min), Math.min(r1.max, r2.max))); + } + + enum RangeOpResultTag { + BEFORE, + OVERLAP, + AFTER, + } + + record RangeOpResult(RangeOpResultTag tag, Range range) { + + } + + private static RangeOpResult rangeUnion(Range r1, Range r2) { + if (r1.max < r2.min) { + if (r1.max + 1 != r2.min) { + return new RangeOpResult(RangeOpResultTag.BEFORE, null); + } + } + if (r2.max < r1.min) { + if (r1.max + 1 != r2.min) { + return new RangeOpResult(RangeOpResultTag.AFTER, null); + } + } + return new RangeOpResult(RangeOpResultTag.OVERLAP, + new Range(Math.min(r1.min, r2.min), Math.max(r1.max, r2.max))); + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BStringSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BStringSubType.java new file mode 100644 index 000000000000..b438ad50ba32 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BStringSubType.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * 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 io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Runtime representation of StringSubType. + * + * @since 2201.10.0 + */ +public final class BStringSubType extends SubType { + + final SubTypeData data; + private static final BStringSubType ALL = new BStringSubType(AllOrNothing.ALL); + private static final BStringSubType NOTHING = new BStringSubType(AllOrNothing.NOTHING); + + private BStringSubType(SubTypeData data) { + super(data == AllOrNothing.ALL, data == AllOrNothing.NOTHING); + this.data = data; + } + + public static BStringSubType createStringSubType(boolean charsAllowed, String[] chars, boolean nonCharsAllowed, + String[] nonChars) { + if (chars.length == 0 && nonChars.length == 0) { + if (!charsAllowed && !nonCharsAllowed) { + return ALL; + } else if (charsAllowed && nonCharsAllowed) { + return NOTHING; + } + } + Arrays.sort(chars); + Arrays.sort(nonChars); + ValueData charValues = new ValueData(charsAllowed, chars); + ValueData nonCharValues = new ValueData(nonCharsAllowed, nonChars); + StringSubTypeData data = new StringSubTypeData(charValues, nonCharValues); + return new BStringSubType(data); + } + + @Override + public String toString() { + if (data instanceof StringSubTypeData stringSubTypeData) { + var chars = stringSubTypeData.chars; + var nonChars = stringSubTypeData.nonChars; + if (chars.allowed && chars.values.length > 0 && nonChars.allowed && nonChars.values.length == 0) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < chars.values.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(chars.values[i]); + } + return sb.toString(); + } else if (nonChars.allowed && nonChars.values.length > 0 && chars.allowed && chars.values.length == 0) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < nonChars.values.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(nonChars.values[i]); + } + return sb.toString(); + } + } + return "string"; + } + + @Override + public SubType union(SubType otherSubType) { + BStringSubType other = (BStringSubType) otherSubType; + // TODO: refactor + if (this.data instanceof AllOrNothing || other.data instanceof AllOrNothing) { + if (this.data == AllOrNothing.ALL) { + return this; + } else if (other.data == AllOrNothing.ALL) { + return other; + } else if (this.data == AllOrNothing.NOTHING) { + return other; + } else if (other.data == AllOrNothing.NOTHING) { + return this; + } + throw new IllegalStateException("unreachable"); + } + StringSubTypeData data = (StringSubTypeData) this.data; + StringSubTypeData otherData = (StringSubTypeData) other.data; + List chars = new ArrayList<>(); + boolean charsAllowed = data.chars.union(otherData.chars, chars); + List nonChars = new ArrayList<>(); + boolean nonCharsAllowed = data.nonChars.union(otherData.nonChars, nonChars); + return createStringSubType(charsAllowed, chars.toArray(String[]::new), nonCharsAllowed, + nonChars.toArray(String[]::new)); + } + + @Override + public SubType intersect(SubType otherSubtype) { + BStringSubType other = (BStringSubType) otherSubtype; + if (this.data instanceof AllOrNothing) { + if (this.data == AllOrNothing.ALL) { + return other; + } else { + return NOTHING; + } + } else if (other.data instanceof AllOrNothing) { + if (other.data == AllOrNothing.ALL) { + return this; + } else { + return NOTHING; + } + } + StringSubTypeData data = (StringSubTypeData) this.data; + StringSubTypeData otherData = (StringSubTypeData) other.data; + List chars = new ArrayList<>(); + boolean charsAllowed = data.chars.intersect(otherData.chars, chars); + List nonChars = new ArrayList<>(); + boolean nonCharsAllowed = data.nonChars.intersect(otherData.nonChars, nonChars); + return createStringSubType(charsAllowed, chars.toArray(String[]::new), nonCharsAllowed, + nonChars.toArray(String[]::new)); + } + + @Override + public SubType complement() { + if (data instanceof AllOrNothing) { + if (data == AllOrNothing.ALL) { + return NOTHING; + } else { + return ALL; + } + } + StringSubTypeData stringData = (StringSubTypeData) data; + ValueData chars = stringData.chars; + ValueData nonChars = stringData.nonChars; + return createStringSubType(!chars.allowed, chars.values, !nonChars.allowed, nonChars.values); + } + + @Override + public boolean isEmpty() { + return data == AllOrNothing.NOTHING; + } + + @Override + public SubTypeData data() { + return data; + } + + private record StringSubTypeData(ValueData chars, ValueData nonChars) implements SubTypeData { + + } + + static final class ValueData extends EnumerableSubtypeData { + + private final boolean allowed; + private final String[] values; + + // NOTE: this assumes values are sorted + private ValueData(boolean allowed, String[] values) { + this.allowed = allowed; + this.values = values; + } + + @Override + boolean allowed() { + return allowed; + } + + @Override + String[] values() { + return values; + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BSubType.java new file mode 100644 index 000000000000..119c3b7e2912 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BSubType.java @@ -0,0 +1,73 @@ +/* + * 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. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SubType; +import io.ballerina.runtime.internal.types.BType; + +/** + * Runtime representation of BType part of a semtype. + * + * @since 2201.10.0 + */ +public class BSubType extends SubType { + + private final BType data; + + private BSubType(BType innerType) { + super(false, false); + data = innerType; + } + + public static BSubType wrap(BType innerType) { + return new BSubType(innerType); + } + + // NOTE: we are allowing isAll() and isNothing() (from the parent) so we can get the union of PureSemTypes and + // PureBTypes. All other operations are unsupported for BSubType + @Override + public SubType union(SubType other) { + throw new IllegalArgumentException("BSubType don't support semType operations"); + } + + @Override + public SubType intersect(SubType other) { + throw new IllegalArgumentException("BSubType don't support semType operations"); + } + + @Override + public SubType diff(SubType other) { + throw new IllegalArgumentException("BSubType don't support semType operations"); + } + + @Override + public SubType complement() { + throw new IllegalArgumentException("BSubType don't support semType operations"); + } + + @Override + public boolean isEmpty() { + throw new IllegalArgumentException("BSubType don't support semType operations"); + } + + @Override + public SubTypeData data() { + return data; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/EnumerableSubtypeData.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/EnumerableSubtypeData.java new file mode 100644 index 000000000000..d3b6a2b7b42b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/EnumerableSubtypeData.java @@ -0,0 +1,163 @@ +/* + * + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * 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 io.ballerina.runtime.internal.types.semtype; + +import java.util.List; + +/** + * All {@code SubTypeData} where we can enumerate individual values must extend this class. It will provide common + * operations such as {@code union}, {@code intersect} and {@code diff} for all such data. + * + * @param type individual value in the subset + * @since 2201.10.0 + */ +abstract class EnumerableSubtypeData> { + + abstract boolean allowed(); + + abstract E[] values(); + + boolean union(EnumerableSubtypeData other, List results) { + boolean b1 = this.allowed(); + boolean b2 = other.allowed(); + if (b1 && b2) { + enumerableListUnion(this.values(), other.values(), results); + return true; + } else if (!b1 && !b2) { + enumerableListIntersection(this.values(), other.values(), results); + return false; + } else if (b1 && !b2) { + enumerableListDiff(other.values(), this.values(), results); + return false; + } else { + enumerableListDiff(this.values(), other.values(), results); + return false; + } + } + + boolean intersect(EnumerableSubtypeData other, List results) { + boolean b1 = this.allowed(); + boolean b2 = other.allowed(); + if (b1 && b2) { + enumerableListIntersection(this.values(), other.values(), results); + return true; + } else if (!b1 && !b2) { + enumerableListUnion(this.values(), other.values(), results); + return false; + } else if (b1 && !b2) { + enumerableListDiff(this.values(), other.values(), results); + return true; + } else { + enumerableListDiff(other.values(), this.values(), results); + return true; + } + } + + private static > void enumerableListUnion(E[] values1, E[] values2, List results) { + int i1, i2; + i1 = i2 = 0; + int len1 = values1.length; + int len2 = values2.length; + while (true) { + if (i1 >= len1) { + if (i2 >= len2) { + break; + } + results.add(values2[i2]); + i2 += 1; + } else if (i2 >= len2) { + results.add(values1[i1]); + i1 += 1; + } else { + E s1 = values1[i1]; + E s2 = values2[i2]; + int result = s1.compareTo(s2); + if (result == 0) { + results.add(s1); + i1 += 1; + i2 += 1; + } else if (result < 0) { + results.add(s1); + i1 += 1; + } else { + results.add(s2); + i2 += 1; + } + } + } + } + + private static > void enumerableListIntersection(E[] v1, E[] v2, List results) { + int i1, i2; + i1 = i2 = 0; + int len1 = v1.length; + int len2 = v2.length; + while (true) { + // TODO: refactor condition + if (i1 >= len1 || i2 >= len2) { + break; + } else { + E s1 = v1[i1]; + E s2 = v2[i2]; + int result = s1.compareTo(s2); + if (result == 0) { + results.add(s1); + i1 += 1; + i2 += 1; + } else if (result < 0) { + i1 += 1; + } else { + i2 += 1; + } + } + } + } + + private static > void enumerableListDiff(E[] t1, E[] t2, List results) { + int i1, i2; + i1 = i2 = 0; + int len1 = t1.length; + int len2 = t2.length; + while (true) { + if (i1 >= len1) { + break; + } + if (i2 >= len2) { + results.add(t1[i1]); + i1 += 1; + } else { + E s1 = t1[i1]; + E s2 = t2[i2]; + int result = s1.compareTo(s2); + if (result == 0) { + i1 += 1; + i2 += 1; + } else if (result < 0) { + results.add(s1); + i1 += 1; + } else { + i2 += 1; + } + } + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/PureSemType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/PureSemType.java new file mode 100644 index 000000000000..ec878f4bb183 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/PureSemType.java @@ -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. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.SubType; + +/** + * Represent types that conform only to {@code SemType} APIs. + * + * @since 2201.10.0 + */ +public final class PureSemType extends SemType { + + public PureSemType(int all, int some, SubType[] subTypeData) { + super(all, some, subTypeData); + } + + public PureSemType(int all) { + super(all); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubTypeData.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubTypeData.java new file mode 100644 index 000000000000..55bb8d32a94c --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubTypeData.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * 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 io.ballerina.runtime.internal.types.semtype; + +/** + * Marker interface for SubTypeData. + * + * @since 2201.10.0 + */ +public interface SubTypeData { + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePair.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePair.java new file mode 100644 index 000000000000..71ac6c9b31c4 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePair.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SubType; + +public record SubtypePair(int typeCode, SubType subType1, SubType subType2) { + + public SubtypePair { + if (subType1 == null && subType2 == null) { + throw new IllegalArgumentException("both subType1 and subType2 cannot be null"); + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePairIterator.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePairIterator.java new file mode 100644 index 000000000000..0676803fcaf1 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePairIterator.java @@ -0,0 +1,75 @@ +/* + * 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. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.Iterator; + +/** + * Iteration implementation of `SubtypePairIterator`. + * + * @since 2201.10.0 + */ +final class SubtypePairIterator implements Iterator { + + // NOTE: this needs to be very efficient since pretty much all type operations depends on it + private int index = 0; + private static final int maxIndex = BasicTypeCode.CODE_B_TYPE + 1; + private final int bits; + private final SemType t1; + private final SemType t2; + + SubtypePairIterator(SemType t1, SemType t2, int bits) { + this.bits = bits; + this.t1 = t1; + this.t2 = t2; + incrementIndex(); + } + + @Override + public boolean hasNext() { + return index < maxIndex; + } + + private void incrementIndex() { + int rest = bits >> index; + int offset = Integer.numberOfTrailingZeros(rest); + index += offset; + } + + private SubType subTypeAtIndex(SemType t, int index) { + if ((t.some() & (1 << index)) != 0) { + return t.subTypeData()[index]; + } + return null; + } + + @Override + public SubtypePair next() { + SubType subType1 = subTypeAtIndex(t1, index); + SubType subType2 = subTypeAtIndex(t2, index); + int typeCode = index; + index++; + incrementIndex(); + return new SubtypePair(typeCode, subType1, subType2); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePairs.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePairs.java new file mode 100644 index 000000000000..07100a7b5ba6 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePairs.java @@ -0,0 +1,46 @@ +/* + * 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. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.Iterator; + +/** + * Implements the iterable for `SubtypePairIteratorImpl`. + * + * @since 2201.10.0 + */ +public class SubtypePairs implements Iterable { + + private final SemType t1; + private final SemType t2; + private final int bits; + + public SubtypePairs(SemType t1, SemType t2, int bits) { + this.t1 = t1; + this.t2 = t2; + this.bits = bits; + } + + @Override + public Iterator iterator() { + return new SubtypePairIterator(t1, t2, bits); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java index 4bf61d2a613b..63d2c88e4f62 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java @@ -614,15 +614,15 @@ public void addRefValue(long index, Object value) { Type type = TypeChecker.getType(value); switch (this.elementReferredType.getTag()) { case TypeTags.BOOLEAN_TAG: - prepareForAdd(index, value, type, booleanValues.length); + prepareForAdd(index, value, booleanValues.length); this.booleanValues[(int) index] = (Boolean) value; return; case TypeTags.FLOAT_TAG: - prepareForAdd(index, value, type, floatValues.length); + prepareForAdd(index, value, floatValues.length); this.floatValues[(int) index] = (Double) value; return; case TypeTags.BYTE_TAG: - prepareForAdd(index, value, type, byteValues.length); + prepareForAdd(index, value, byteValues.length); this.byteValues[(int) index] = ((Number) value).byteValue(); return; case TypeTags.INT_TAG: @@ -632,16 +632,16 @@ public void addRefValue(long index, Object value) { case TypeTags.UNSIGNED32_INT_TAG: case TypeTags.UNSIGNED16_INT_TAG: case TypeTags.UNSIGNED8_INT_TAG: - prepareForAdd(index, value, type, intValues.length); + prepareForAdd(index, value, intValues.length); this.intValues[(int) index] = (Long) value; return; case TypeTags.STRING_TAG: case TypeTags.CHAR_STRING_TAG: - prepareForAdd(index, value, type, bStringValues.length); + prepareForAdd(index, value, bStringValues.length); this.bStringValues[(int) index] = (BString) value; return; default: - prepareForAdd(index, value, type, refValues.length); + prepareForAdd(index, value, refValues.length); this.refValues[(int) index] = value; } } @@ -659,27 +659,27 @@ public void setArrayRefTypeForcefully(ArrayType type, int size) { public void addInt(long index, long value) { if (intValues != null) { - prepareForAdd(index, value, PredefinedTypes.TYPE_INT, intValues.length); + prepareForAdd(index, value, intValues.length); intValues[(int) index] = value; return; } - prepareForAdd(index, value, TypeChecker.getType(value), byteValues.length); + prepareForAdd(index, value, byteValues.length); byteValues[(int) index] = (byte) ((Long) value).intValue(); } private void addBoolean(long index, boolean value) { - prepareForAdd(index, value, PredefinedTypes.TYPE_BOOLEAN, booleanValues.length); + prepareForAdd(index, value, booleanValues.length); booleanValues[(int) index] = value; } private void addByte(long index, byte value) { - prepareForAdd(index, value, PredefinedTypes.TYPE_BYTE, byteValues.length); + prepareForAdd(index, value, byteValues.length); byteValues[(int) index] = value; } private void addFloat(long index, double value) { - prepareForAdd(index, value, PredefinedTypes.TYPE_FLOAT, floatValues.length); + prepareForAdd(index, value, floatValues.length); floatValues[(int) index] = value; } @@ -689,7 +689,7 @@ private void addString(long index, String value) { } private void addBString(long index, BString value) { - prepareForAdd(index, value, PredefinedTypes.TYPE_STRING, bStringValues.length); + prepareForAdd(index, value, bStringValues.length); bStringValues[(int) index] = value; } @@ -1246,12 +1246,12 @@ protected void unshift(long index, Object[] vals) { // Private methods - private void prepareForAdd(long index, Object value, Type sourceType, int currentArraySize) { + private void prepareForAdd(long index, Object value, int currentArraySize) { // check types - if (!TypeChecker.checkIsType(null, value, sourceType, this.elementType)) { + if (!TypeChecker.checkIsType(value, this.elementType)) { throw ErrorCreator.createError(getModulePrefixedReason(ARRAY_LANG_LIB, INHERENT_TYPE_VIOLATION_ERROR_IDENTIFIER), ErrorHelper.getErrorDetails( - ErrorCodes.INCOMPATIBLE_TYPE, this.elementType, sourceType)); + ErrorCodes.INCOMPATIBLE_TYPE, this.elementType, TypeChecker.getType(value))); } prepareForAddWithoutTypeCheck(index, currentArraySize); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/DecimalValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/DecimalValue.java index aafa40069354..6b774678846d 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/DecimalValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/DecimalValue.java @@ -29,6 +29,7 @@ import io.ballerina.runtime.internal.errors.ErrorCodes; import io.ballerina.runtime.internal.errors.ErrorHelper; import io.ballerina.runtime.internal.errors.ErrorReasons; +import io.ballerina.runtime.internal.types.BDecimalType; import java.math.BigDecimal; import java.math.MathContext; @@ -61,8 +62,10 @@ public class DecimalValue implements SimpleValue, BDecimal { public DecimalValueKind valueKind = DecimalValueKind.OTHER; private final BigDecimal value; + private final Type singletonType; public DecimalValue(BigDecimal value) { + this.singletonType = BDecimalType.singletonType(value); this.value = getValidDecimalValue(value); if (!this.booleanValue()) { this.valueKind = DecimalValueKind.ZERO; @@ -84,7 +87,7 @@ public DecimalValue(String value) { throw exception; } this.value = getValidDecimalValue(bd); - + this.singletonType = BDecimalType.singletonType(this.value); if (!this.booleanValue()) { this.valueKind = DecimalValueKind.ZERO; } @@ -221,7 +224,7 @@ public BigDecimal value() { * @return the type */ public Type getType() { - return PredefinedTypes.TYPE_DECIMAL; + return singletonType; } //========================= Mathematical operations supported =============================== diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/StringValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/StringValue.java index a00c1c0e0575..066ecb1cbc92 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/StringValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/StringValue.java @@ -17,10 +17,10 @@ */ package io.ballerina.runtime.internal.values; -import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.values.BLink; import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.internal.types.BStringType; import java.util.Map; @@ -33,15 +33,17 @@ public abstract class StringValue implements BString, SimpleValue { final String value; final boolean isNonBmp; + private final Type type; protected StringValue(String value, boolean isNonBmp) { this.value = value; this.isNonBmp = isNonBmp; + this.type = BStringType.singletonType(value); } @Override public Type getType() { - return PredefinedTypes.TYPE_STRING; + return type; } @Override diff --git a/bvm/ballerina-runtime/src/main/java/module-info.java b/bvm/ballerina-runtime/src/main/java/module-info.java index f34060732a90..b5df316d239c 100644 --- a/bvm/ballerina-runtime/src/main/java/module-info.java +++ b/bvm/ballerina-runtime/src/main/java/module-info.java @@ -28,6 +28,7 @@ exports io.ballerina.runtime.api.types; exports io.ballerina.runtime.api.utils; exports io.ballerina.runtime.api.values; + exports io.ballerina.runtime.api.types.semtype; exports io.ballerina.runtime.observability; exports io.ballerina.runtime.observability.metrics; diff --git a/semtypes/src/main/java/io/ballerina/types/subtypedata/StringSubtype.java b/semtypes/src/main/java/io/ballerina/types/subtypedata/StringSubtype.java index dda6ea60d977..edd0c4dad869 100644 --- a/semtypes/src/main/java/io/ballerina/types/subtypedata/StringSubtype.java +++ b/semtypes/src/main/java/io/ballerina/types/subtypedata/StringSubtype.java @@ -35,6 +35,8 @@ */ public class StringSubtype implements ProperSubtypeData { + private static final EnumerableString[] EMPTY_STRING_ARR = {}; + private static final EnumerableCharString[] EMPTY_CHAR_ARR = {}; CharStringSubtype charData; NonCharStringSubtype nonCharData; @@ -98,12 +100,12 @@ public static Optional stringSubtypeSingleValue(SubtypeData d) { public static SemType stringConst(String value) { CharStringSubtype chara; NonCharStringSubtype nonChar; - if (value.length() == 1) { + if (value.codePointCount(0, value.length()) == 1) { chara = CharStringSubtype.from(true, new EnumerableCharString[]{EnumerableCharString.from(value)}); - nonChar = NonCharStringSubtype.from(true, new EnumerableString[]{}); + nonChar = NonCharStringSubtype.from(true, EMPTY_STRING_ARR); } else { - chara = CharStringSubtype.from(true, new EnumerableCharString[]{}); + chara = CharStringSubtype.from(true, EMPTY_CHAR_ARR); nonChar = NonCharStringSubtype.from(true, new EnumerableString[]{EnumerableString.from(value)}); } return PredefinedType.basicSubtype(BasicTypeCode.BT_STRING, new StringSubtype(chara, nonChar)); @@ -111,8 +113,8 @@ public static SemType stringConst(String value) { public static SemType stringChar() { StringSubtype st = new StringSubtype( - CharStringSubtype.from(false, new EnumerableCharString[]{}), - NonCharStringSubtype.from(true, new EnumerableString[]{})); + CharStringSubtype.from(false, EMPTY_CHAR_ARR), + NonCharStringSubtype.from(true, EMPTY_STRING_ARR)); return PredefinedType.basicSubtype(BasicTypeCode.BT_STRING, st); } diff --git a/semtypes/src/main/java/io/ballerina/types/typeops/FloatOps.java b/semtypes/src/main/java/io/ballerina/types/typeops/FloatOps.java index e674317ef86e..1264bbcf9671 100644 --- a/semtypes/src/main/java/io/ballerina/types/typeops/FloatOps.java +++ b/semtypes/src/main/java/io/ballerina/types/typeops/FloatOps.java @@ -56,7 +56,7 @@ public SubtypeData diff(SubtypeData t1, SubtypeData t2) { @Override public SubtypeData complement(SubtypeData t) { FloatSubtype s = (FloatSubtype) t; - return FloatSubtype.createFloatSubtype(!s.allowed, (EnumerableFloat[]) s.values); + return FloatSubtype.createFloatSubtype(!s.allowed, s.values); } @Override diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerSemTypeResolver.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerSemTypeResolver.java new file mode 100644 index 000000000000..e181b2232342 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerSemTypeResolver.java @@ -0,0 +1,720 @@ +/* + * 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. + */ +package io.ballerina.semtype.port.test; + +import io.ballerina.types.CellAtomicType; +import io.ballerina.types.Context; +import io.ballerina.types.Core; +import io.ballerina.types.Definition; +import io.ballerina.types.Env; +import io.ballerina.types.PredefinedType; +import io.ballerina.types.SemType; +import io.ballerina.types.SemTypes; +import io.ballerina.types.definition.Field; +import io.ballerina.types.definition.FunctionDefinition; +import io.ballerina.types.definition.FunctionQualifiers; +import io.ballerina.types.definition.ListDefinition; +import io.ballerina.types.definition.MappingDefinition; +import io.ballerina.types.definition.Member; +import io.ballerina.types.definition.ObjectDefinition; +import io.ballerina.types.definition.ObjectQualifiers; +import io.ballerina.types.definition.StreamDefinition; +import io.ballerina.types.subtypedata.FloatSubtype; +import org.ballerinalang.model.elements.Flag; +import org.ballerinalang.model.tree.NodeKind; +import org.ballerinalang.model.tree.types.ArrayTypeNode; +import org.ballerinalang.model.tree.types.TypeNode; +import org.ballerinalang.model.types.TypeKind; +import org.wso2.ballerinalang.compiler.tree.BLangFunction; +import org.wso2.ballerinalang.compiler.tree.BLangNode; +import org.wso2.ballerinalang.compiler.tree.BLangSimpleVariable; +import org.wso2.ballerinalang.compiler.tree.BLangTypeDefinition; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangConstant; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangLiteral; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangSimpleVarRef; +import org.wso2.ballerinalang.compiler.tree.types.BLangArrayType; +import org.wso2.ballerinalang.compiler.tree.types.BLangBuiltInRefTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangConstrainedType; +import org.wso2.ballerinalang.compiler.tree.types.BLangErrorType; +import org.wso2.ballerinalang.compiler.tree.types.BLangFiniteTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangFunctionTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangIntersectionTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangObjectTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangRecordTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangStreamType; +import org.wso2.ballerinalang.compiler.tree.types.BLangTableTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangTupleTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangType; +import org.wso2.ballerinalang.compiler.tree.types.BLangUnionTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangUserDefinedType; +import org.wso2.ballerinalang.compiler.tree.types.BLangValueType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import static org.wso2.ballerinalang.compiler.semantics.analyzer.SymbolEnter.getTypeOrClassName; + +/** + * Resolves sem-types for module definitions. + * + * @since 2201.10.0 + */ +public class CompilerSemTypeResolver extends SemTypeResolver { + + private final Map attachedDefinitions = new HashMap<>(); + + public void defineSemTypes(List moduleDefs, TypeTestContext cx) { + Map modTable = new LinkedHashMap<>(); + for (BLangNode typeAndClassDef : moduleDefs) { + modTable.put(getTypeOrClassName(typeAndClassDef), typeAndClassDef); + } + modTable = Collections.unmodifiableMap(modTable); + + for (BLangNode def : moduleDefs) { + if (def.getKind() == NodeKind.CLASS_DEFN) { + throw new UnsupportedOperationException("Semtype are not supported for class definitions yet"); + } else if (def.getKind() == NodeKind.CONSTANT) { + resolveConstant(cx, modTable, (BLangConstant) def); + } else { + BLangTypeDefinition typeDefinition = (BLangTypeDefinition) def; + resolveTypeDefn(cx, modTable, typeDefinition, 0); + } + } + } + + @Override + protected void resolveConstant(TypeTestContext cx, Map modTable, + BLangConstant constant) { + SemType semtype = evaluateConst(constant); + addSemTypeBType(constant.getTypeNode(), semtype); + cx.getEnv().addTypeDef(constant.name.value, semtype); + } + + private SemType evaluateConst(BLangConstant constant) { + switch (constant.symbol.value.type.getKind()) { + case INT: + return SemTypes.intConst((long) constant.symbol.value.value); + case BOOLEAN: + return SemTypes.booleanConst((boolean) constant.symbol.value.value); + case STRING: + return SemTypes.stringConst((String) constant.symbol.value.value); + case FLOAT: + return SemTypes.floatConst((double) constant.symbol.value.value); + default: + throw new UnsupportedOperationException("Expression type not implemented for const semtype"); + } + } + + @Override + protected void resolveTypeDefn(TypeTestContext cx, Map mod, + BLangTypeDefinition defn) { + resolveTypeDefn(cx, mod, defn, 0); + } + + private SemType resolveTypeDefn(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth) { + if (defn.semType != null) { + return defn.semType; + } + + if (depth == defn.semCycleDepth) { + throw new IllegalStateException("cyclic type definition: " + defn.name.value); + } + defn.semCycleDepth = depth; + SemType s = resolveTypeDesc(cx, mod, defn, depth, defn.typeNode); + addSemTypeBType(defn.getTypeNode(), s); + if (defn.semType == null) { + defn.semType = s; + defn.semCycleDepth = -1; + cx.getEnv().addTypeDef(defn.name.value, s); + return s; + } else { + return s; + } + } + + private void addSemTypeBType(BLangType typeNode, SemType semType) { + if (typeNode != null) { + typeNode.getBType().semType(semType); + } + } + + public SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, TypeNode td) { + if (td == null) { + return null; + } + switch (td.getKind()) { + case VALUE_TYPE: + return resolveTypeDesc(cx, (BLangValueType) td); + case BUILT_IN_REF_TYPE: + return resolveTypeDesc(cx, (BLangBuiltInRefTypeNode) td); + case RECORD_TYPE: + return resolveTypeDesc(cx, (BLangRecordTypeNode) td, mod, depth, defn); + case CONSTRAINED_TYPE: // map and typedesc + return resolveTypeDesc(cx, (BLangConstrainedType) td, mod, depth, defn); + case UNION_TYPE_NODE: + return resolveTypeDesc(cx, (BLangUnionTypeNode) td, mod, depth, defn); + case INTERSECTION_TYPE_NODE: + return resolveTypeDesc(cx, (BLangIntersectionTypeNode) td, mod, depth, defn); + case USER_DEFINED_TYPE: + return resolveTypeDesc(cx, (BLangUserDefinedType) td, mod, depth); + case FINITE_TYPE_NODE: + return resolveSingletonType((BLangFiniteTypeNode) td); + case ARRAY_TYPE: + return resolveTypeDesc(cx, mod, defn, depth, (BLangArrayType) td); + case TUPLE_TYPE_NODE: + return resolveTypeDesc(cx, mod, defn, depth, (BLangTupleTypeNode) td); + case FUNCTION_TYPE: + return resolveTypeDesc(cx, mod, defn, depth, (BLangFunctionTypeNode) td); + case TABLE_TYPE: + return resolveTypeDesc(cx, mod, defn, depth, (BLangTableTypeNode) td); + case ERROR_TYPE: + return resolveTypeDesc(cx, mod, defn, depth, (BLangErrorType) td); + case OBJECT_TYPE: + return resolveTypeDesc(cx, mod, defn, depth, (BLangObjectTypeNode) td); + case STREAM_TYPE: + return resolveTypeDesc(cx, mod, defn, depth, (BLangStreamType) td); + default: + throw new UnsupportedOperationException("type not implemented: " + td.getKind()); + } + } + + private SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, BLangObjectTypeNode td) { + SemType innerType = resolveNonDistinctObject(cx, mod, defn, depth, td); + if (td.flagSet.contains(Flag.DISTINCT)) { + return getDistinctObjectType((Env) cx.getInnerEnv(), innerType); + } + return innerType; + } + + private static SemType getDistinctObjectType(Env env, SemType innerType) { + return Core.intersect(SemTypes.objectDistinct(env.distinctAtomCountGetAndIncrement()), innerType); + } + + private SemType resolveNonDistinctObject(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, + int depth, BLangObjectTypeNode td) { + Env env = (Env) cx.getInnerEnv(); + if (td.defn != null) { + return td.defn.getSemType(env); + } + ObjectDefinition od = new ObjectDefinition(); + Stream fieldStream = td.fields.stream().map(field -> { + Set flags = field.flagSet; + Member.Visibility visibility = flags.contains(Flag.PUBLIC) ? Member.Visibility.Public : + Member.Visibility.Private; + SemType ty = resolveTypeDesc(cx, mod, defn, depth + 1, field.typeNode); + return new Member(field.name.value, ty, Member.Kind.Field, visibility, flags.contains(Flag.READONLY)); + }); + Stream methodStream = td.getFunctions().stream().map(method -> { + Member.Visibility visibility = method.flagSet.contains(Flag.PUBLIC) ? Member.Visibility.Public : + Member.Visibility.Private; + SemType ty = resolveTypeDesc(cx, mod, defn, depth + 1, method); + return new Member(method.name.value, ty, Member.Kind.Method, visibility, true); + }); + td.defn = od; + List members = Stream.concat(fieldStream, methodStream).toList(); + ObjectQualifiers qualifiers = getQualifiers(td); + return od.define(env, qualifiers, members); + } + + private static ObjectQualifiers getQualifiers(BLangObjectTypeNode td) { + Set flags = td.symbol.getFlags(); + ObjectQualifiers.NetworkQualifier networkQualifier; + assert !(flags.contains(Flag.CLIENT) && flags.contains(Flag.SERVICE)) : + "object can't be both client and service"; + if (flags.contains(Flag.CLIENT)) { + networkQualifier = ObjectQualifiers.NetworkQualifier.Client; + } else if (flags.contains(Flag.SERVICE)) { + networkQualifier = ObjectQualifiers.NetworkQualifier.Service; + } else { + networkQualifier = ObjectQualifiers.NetworkQualifier.None; + } + return new ObjectQualifiers(flags.contains(Flag.ISOLATED), flags.contains(Flag.READONLY), networkQualifier); + } + + // TODO: should we make definition part of BLangFunction as well? + private SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, BLangFunction functionType) { + Definition attached = attachedDefinitions.get(functionType); + Env env = (Env) cx.getInnerEnv(); + if (attached != null) { + return attached.getSemType(env); + } + FunctionDefinition fd = new FunctionDefinition(); + attachedDefinitions.put(functionType, fd); + List params = functionType.getParameters().stream() + .map(paramVar -> resolveTypeDesc(cx, mod, defn, depth + 1, paramVar.typeNode)).toList(); + SemType rest; + if (functionType.getRestParameters() == null) { + rest = PredefinedType.NEVER; + } else { + ArrayTypeNode arrayType = (ArrayTypeNode) functionType.getRestParameters().getTypeNode(); + rest = resolveTypeDesc(cx, mod, defn, depth + 1, arrayType.getElementType()); + } + SemType returnType = functionType.getReturnTypeNode() != null ? + resolveTypeDesc(cx, mod, defn, depth + 1, functionType.getReturnTypeNode()) : PredefinedType.NIL; + ListDefinition paramListDefinition = new ListDefinition(); + FunctionQualifiers qualifiers = FunctionQualifiers.from(env, functionType.flagSet.contains(Flag.ISOLATED), + functionType.flagSet.contains(Flag.TRANSACTIONAL)); + return fd.define(env, paramListDefinition.defineListTypeWrapped(env, params, params.size(), rest, + CellAtomicType.CellMutability.CELL_MUT_NONE), returnType, qualifiers); + } + + private SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, BLangFunctionTypeNode td) { + Env env = (Env) cx.getInnerEnv(); + if (isFunctionTop(td)) { + if (td.flagSet.contains(Flag.ISOLATED) || td.flagSet.contains(Flag.TRANSACTIONAL)) { + FunctionQualifiers qualifiers = FunctionQualifiers.from(env, td.flagSet.contains(Flag.ISOLATED), + td.flagSet.contains(Flag.TRANSACTIONAL)); + // I think param type here is wrong. It should be the intersection of all list types, but I think + // never is close enough + return new FunctionDefinition().define(env, PredefinedType.NEVER, PredefinedType.VAL, qualifiers); + } + return PredefinedType.FUNCTION; + } + if (td.defn != null) { + return td.defn.getSemType((Env) cx.getInnerEnv()); + } + FunctionDefinition fd = new FunctionDefinition(); + td.defn = fd; + List params = + td.params.stream().map(param -> resolveTypeDesc(cx, mod, defn, depth + 1, param.typeNode)) + .toList(); + SemType rest; + if (td.restParam == null) { + rest = PredefinedType.NEVER; + } else { + BLangArrayType restArrayType = (BLangArrayType) td.restParam.typeNode; + rest = resolveTypeDesc(cx, mod, defn, depth + 1, restArrayType.elemtype); + } + SemType returnType = td.returnTypeNode != null ? resolveTypeDesc(cx, mod, defn, depth + 1, td.returnTypeNode) : + PredefinedType.NIL; + ListDefinition paramListDefinition = new ListDefinition(); + FunctionQualifiers qualifiers = FunctionQualifiers.from(env, td.flagSet.contains(Flag.ISOLATED), + td.flagSet.contains(Flag.TRANSACTIONAL)); + return fd.define(env, paramListDefinition.defineListTypeWrapped(env, params, params.size(), rest, + CellAtomicType.CellMutability.CELL_MUT_NONE), returnType, qualifiers); + } + + private boolean isFunctionTop(BLangFunctionTypeNode td) { + return td.params.isEmpty() && td.restParam == null && td.returnTypeNode == null; + } + + private SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, BLangArrayType td) { + if (td.defn != null) { + return td.defn.getSemType((Env) cx.getInnerEnv()); + } + ListDefinition ld = new ListDefinition(); + td.defn = ld; + + int dimensions = td.dimensions; + SemType accum = resolveTypeDesc(cx, mod, defn, depth + 1, td.elemtype); + for (int i = 0; i < dimensions; i++) { + int size = from(mod, td.sizes.get(i)); + if (i == dimensions - 1) { + accum = resolveListInner(cx, ld, size, accum); + } else { + accum = resolveListInner(cx, size, accum); + } + } + return accum; + } + + private static int from(Map mod, BLangNode expr) { + if (expr instanceof BLangLiteral literal) { + return listSize((Number) literal.value); + } else if (expr instanceof BLangSimpleVarRef varRef) { + String varName = varRef.variableName.value; + return from(mod, mod.get(varName)); + } else if (expr instanceof BLangConstant constant) { + return listSize((Number) constant.symbol.value.value); + } + throw new UnsupportedOperationException("Unsupported expr kind " + expr.getKind()); + } + + private static int listSize(Number size) { + if (size.longValue() > Integer.MAX_VALUE) { + throw new IllegalArgumentException("list sizes greater than " + Integer.MAX_VALUE + " not yet supported"); + } + return size.intValue(); + } + + private SemType resolveListInner(TypeTestContext cx, int size, SemType eType) { + ListDefinition ld = new ListDefinition(); + return resolveListInner(cx, ld, size, eType); + } + + private static SemType resolveListInner(TypeTestContext cx, ListDefinition ld, int size, SemType eType) { + Env env = (Env) cx.getInnerEnv(); + if (size != -1) { + return ld.defineListTypeWrapped(env, List.of(eType), Math.abs(size), PredefinedType.NEVER, + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + } else { + return ld.defineListTypeWrapped(env, List.of(), 0, eType, + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + } + } + + private SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, + BLangTupleTypeNode td) { + Env env = (Env) cx.getInnerEnv(); + if (td.defn != null) { + return td.defn.getSemType(env); + } + ListDefinition ld = new ListDefinition(); + td.defn = ld; + List memberSemTypes = + td.members.stream().map(member -> resolveTypeDesc(cx, mod, defn, depth + 1, member.typeNode)) + .toList(); + SemType rest = td.restParamType != null ? resolveTypeDesc(cx, mod, defn, depth + 1, td.restParamType) : + PredefinedType.NEVER; + return ld.defineListTypeWrapped(env, memberSemTypes, memberSemTypes.size(), rest); + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangValueType td) { + Context innerContext = (Context) cx.getInnerContext(); + switch (td.typeKind) { + case NIL: + return PredefinedType.NIL; + case BOOLEAN: + return PredefinedType.BOOLEAN; + case BYTE: + return PredefinedType.BYTE; + case INT: + return PredefinedType.INT; + case FLOAT: + return PredefinedType.FLOAT; + case DECIMAL: + return PredefinedType.DECIMAL; + case STRING: + return PredefinedType.STRING; + case TYPEDESC: + return PredefinedType.TYPEDESC; + case ERROR: + return PredefinedType.ERROR; + case HANDLE: + return PredefinedType.HANDLE; + case XML: + return PredefinedType.XML; + case ANY: + return PredefinedType.ANY; + case READONLY: + return PredefinedType.VAL_READONLY; + case ANYDATA: + return Core.createAnydata(innerContext); + case JSON: + return Core.createJson(innerContext); + default: + throw new IllegalStateException("Unknown type: " + td); + } + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangBuiltInRefTypeNode td) { + return switch (td.typeKind) { + case NEVER -> PredefinedType.NEVER; + case XML -> PredefinedType.XML; + case JSON -> Core.createJson((Context) cx.getInnerContext()); + default -> throw new UnsupportedOperationException("Built-in ref type not implemented: " + td.typeKind); + }; + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangConstrainedType td, Map mod, + int depth, BLangTypeDefinition defn) { + TypeKind typeKind = ((BLangBuiltInRefTypeNode) td.getType()).getTypeKind(); + return switch (typeKind) { + case MAP -> resolveMapTypeDesc(td, cx, mod, depth, defn); + case XML -> resolveXmlTypeDesc(td, cx, mod, depth, defn); + case FUTURE -> resolveFutureTypeDesc(td, cx, mod, depth, defn); + case TYPEDESC -> resolveTypedescTypeDesc(td, cx, mod, depth, defn); + default -> throw new IllegalStateException("Unexpected constrained type: " + typeKind); + }; + } + + private SemType resolveMapTypeDesc(BLangConstrainedType td, TypeTestContext cx, Map mod, + int depth, BLangTypeDefinition typeDefinition) { + Env env = (Env) cx.getInnerEnv(); + if (td.defn != null) { + return td.defn.getSemType(env); + } + + MappingDefinition d = new MappingDefinition(); + td.defn = d; + + SemType rest = resolveTypeDesc(cx, mod, typeDefinition, depth + 1, td.constraint); + return d.defineMappingTypeWrapped(env, Collections.emptyList(), rest == null ? PredefinedType.NEVER : rest); + } + + private SemType resolveXmlTypeDesc(BLangConstrainedType td, TypeTestContext cx, Map mod, + int depth, + BLangTypeDefinition defn) { + SemType constraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + return SemTypes.xmlSequence(constraint); + } + + private SemType resolveFutureTypeDesc(BLangConstrainedType td, TypeTestContext cx, + Map mod, int depth, + BLangTypeDefinition defn) { + SemType constraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + return SemTypes.futureContaining((Env) cx.getInnerEnv(), constraint); + } + + private SemType resolveTypedescTypeDesc(BLangConstrainedType td, TypeTestContext cx, + Map mod, int depth, + BLangTypeDefinition defn) { + SemType constraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + return SemTypes.typedescContaining((Env) cx.getInnerEnv(), constraint); + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangRecordTypeNode td, Map mod, + int depth, BLangTypeDefinition typeDefinition) { + if (td.defn != null) { + return td.defn.getSemType((Env) cx.getInnerEnv()); + } + + MappingDefinition d = new MappingDefinition(); + td.defn = d; + + List fields = new ArrayList<>(); + for (BLangSimpleVariable field : td.fields) { + SemType ty = resolveTypeDesc(cx, mod, typeDefinition, depth + 1, field.typeNode); + if (Core.isNever(ty)) { + throw new IllegalStateException("record field can't be never"); + } + fields.add(Field.from(field.name.value, ty, false, field.flagSet.contains(Flag.OPTIONAL))); + } + + SemType rest; + if (!td.isSealed() && td.getRestFieldType() == null) { + rest = Core.createAnydata((Context) cx.getInnerContext()); + } else { + rest = resolveTypeDesc(cx, mod, typeDefinition, depth + 1, td.restFieldType); + } + + return d.defineMappingTypeWrapped((Env) cx.getInnerEnv(), fields, rest == null ? PredefinedType.NEVER : rest); + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangUnionTypeNode td, Map mod, + int depth, + BLangTypeDefinition defn) { + Iterator iterator = td.memberTypeNodes.iterator(); + SemType u = resolveTypeDesc(cx, mod, defn, depth, iterator.next()); + while (iterator.hasNext()) { + u = Core.union(u, resolveTypeDesc(cx, mod, defn, depth, iterator.next())); + } + return u; + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangIntersectionTypeNode td, + Map mod, int depth, + BLangTypeDefinition defn) { + Iterator iterator = td.constituentTypeNodes.iterator(); + SemType i = resolveTypeDesc(cx, mod, defn, depth, iterator.next()); + while (iterator.hasNext()) { + i = Core.intersect(i, resolveTypeDesc(cx, mod, defn, depth, iterator.next())); + } + return i; + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangUserDefinedType td, Map mod, + int depth) { + String name = td.typeName.value; + // Need to replace this with a real package lookup + if (td.pkgAlias.value.equals("int")) { + return resolveIntSubtype(name); + } else if (td.pkgAlias.value.equals("string") && name.equals("Char")) { + return SemTypes.CHAR; + } else if (td.pkgAlias.value.equals("xml")) { + return resolveXmlSubtype(name); + } else if (td.pkgAlias.value.equals("regexp") && name.equals("RegExp")) { + return PredefinedType.REGEXP; + } + + BLangNode moduleLevelDef = mod.get(name); + if (moduleLevelDef == null) { + throw new IllegalStateException("unknown type: " + name); + } + + if (moduleLevelDef.getKind() == NodeKind.TYPE_DEFINITION) { + SemType ty = resolveTypeDefn(cx, mod, (BLangTypeDefinition) moduleLevelDef, depth); + if (td.flagSet.contains(Flag.DISTINCT)) { + return getDistinctSemType(cx, ty); + } + return ty; + } else if (moduleLevelDef.getKind() == NodeKind.CONSTANT) { + BLangConstant constant = (BLangConstant) moduleLevelDef; + return resolveTypeDefn(cx, mod, constant.associatedTypeDefinition, depth); + } else { + throw new UnsupportedOperationException("constants and class defns not implemented"); + } + } + + private SemType getDistinctSemType(TypeTestContext cx, SemType innerType) { + Env env = (Env) cx.getInnerEnv(); + if (Core.isSubtypeSimple(innerType, PredefinedType.OBJECT)) { + return getDistinctObjectType(env, innerType); + } else if (Core.isSubtypeSimple(innerType, PredefinedType.ERROR)) { + return getDistinctErrorType(env, innerType); + } + throw new IllegalArgumentException("Distinct type not supported for: " + innerType); + } + + private SemType resolveIntSubtype(String name) { + // TODO: support MAX_VALUE + return switch (name) { + case "Signed8" -> SemTypes.SINT8; + case "Signed16" -> SemTypes.SINT16; + case "Signed32" -> SemTypes.SINT32; + case "Unsigned8" -> SemTypes.UINT8; + case "Unsigned16" -> SemTypes.UINT16; + case "Unsigned32" -> SemTypes.UINT32; + default -> throw new UnsupportedOperationException("Unknown int subtype: " + name); + }; + } + + private SemType resolveXmlSubtype(String name) { + return switch (name) { + case "Element" -> SemTypes.XML_ELEMENT; + case "Comment" -> SemTypes.XML_COMMENT; + case "Text" -> SemTypes.XML_TEXT; + case "ProcessingInstruction" -> SemTypes.XML_PI; + default -> throw new IllegalStateException("Unknown XML subtype: " + name); + }; + } + + private SemType resolveSingletonType(BLangFiniteTypeNode td) { + return resolveSingletonType(td.valueSpace); + } + + private SemType resolveSingletonType(List valueSpace) { + List types = new ArrayList<>(); + for (BLangExpression bLangExpression : valueSpace) { + types.add(resolveSingletonType((BLangLiteral) bLangExpression)); + } + + Iterator iter = types.iterator(); + SemType u = iter.next(); + while (iter.hasNext()) { + u = SemTypes.union(u, iter.next()); + } + return u; + } + + private SemType resolveSingletonType(BLangLiteral literal) { + return resolveSingletonType(literal.value, literal.getDeterminedType().getKind()); + } + + private SemType resolveSingletonType(Object value, TypeKind targetTypeKind) { + return switch (targetTypeKind) { + case NIL -> PredefinedType.NIL; + case BOOLEAN -> SemTypes.booleanConst((Boolean) value); + case INT, BYTE -> { + assert !(value instanceof Byte); + yield SemTypes.intConst(((Number) value).longValue()); + } + case FLOAT -> { + double doubleVal; + if (value instanceof Long longValue) { + doubleVal = longValue.doubleValue(); + } else if (value instanceof Double doubleValue) { + doubleVal = doubleValue; + } else { + // literal value will be a string if it wasn't within the bounds of what is supported by Java Long + // or Double when it was parsed in BLangNodeBuilder. + try { + doubleVal = Double.parseDouble((String) value); + } catch (NumberFormatException e) { + // We reach here when there is a syntax error. Mock the flow with default float value. + yield FloatSubtype.floatConst(0); + } + } + yield SemTypes.floatConst(doubleVal); + // literal value will be a string if it wasn't within the bounds of what is supported by Java Long + // or Double when it was parsed in BLangNodeBuilder. + // We reach here when there is a syntax error. Mock the flow with default float value. + } + case DECIMAL -> SemTypes.decimalConst((String) value); + case STRING -> SemTypes.stringConst((String) value); + default -> throw new UnsupportedOperationException("Finite type not implemented for: " + targetTypeKind); + }; + } + + private SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, + BLangTableTypeNode td) { + if (td.tableKeySpecifier != null || td.tableKeyTypeConstraint != null) { + throw new UnsupportedOperationException("table key constraint not supported yet"); + } + + SemType memberType = resolveTypeDesc(cx, mod, defn, depth, td.constraint); + return SemTypes.tableContaining((Env) cx.getInnerEnv(), memberType); + } + + private SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, + BLangErrorType td) { + SemType err; + if (td.detailType == null) { + err = PredefinedType.ERROR; + } else { + SemType detail = resolveTypeDesc(cx, mod, defn, depth, td.detailType); + err = SemTypes.errorDetail(detail); + } + + if (td.flagSet.contains(Flag.DISTINCT)) { + Env env = (Env) cx.getInnerEnv(); + err = getDistinctErrorType(env, err); + } + return err; + } + + private static SemType getDistinctErrorType(Env env, SemType err) { + return Core.intersect(SemTypes.errorDistinct(env.distinctAtomCountGetAndIncrement()), err); + } + + private SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, BLangStreamType td) { + if (td.constraint == null) { + return PredefinedType.STREAM; + } + Env env = (Env) cx.getInnerEnv(); + if (td.defn != null) { + return td.defn.getSemType(env); + } + StreamDefinition d = new StreamDefinition(); + td.defn = d; + + SemType valueType = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + SemType completionType = td.error == null ? + PredefinedType.NIL : resolveTypeDesc(cx, mod, defn, depth + 1, td.error); + return d.define(env, valueType, completionType); + } +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerTypeTestAPI.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerTypeTestAPI.java new file mode 100644 index 000000000000..71870e480870 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerTypeTestAPI.java @@ -0,0 +1,81 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +import io.ballerina.types.BasicTypeBitSet; +import io.ballerina.types.Context; +import io.ballerina.types.PredefinedType; +import io.ballerina.types.SemType; +import io.ballerina.types.SemTypes; + +public final class CompilerTypeTestAPI implements TypeTestAPI { + + private static final CompilerTypeTestAPI INSTANCE = new CompilerTypeTestAPI(); + + private CompilerTypeTestAPI() { + } + + public static CompilerTypeTestAPI getInstance() { + return INSTANCE; + } + + @Override + public boolean isSubtype(TypeTestContext cx, SemType t1, SemType t2) { + return SemTypes.isSubtype(form(cx), t1, t2); + } + + private static Context form(TypeTestContext cx) { + return (Context) cx.getInnerContext(); + } + + @Override + public boolean isSubtypeSimple(SemType t1, SemType t2) { + return SemTypes.isSubtypeSimple(t1, (BasicTypeBitSet) t2); + } + + @Override + public boolean isListType(SemType t) { + return SemTypes.isSubtypeSimple(t, PredefinedType.LIST); + } + + @Override + public boolean isMapType(SemType t) { + return SemTypes.isSubtypeSimple(t, PredefinedType.MAPPING); + } + + @Override + public SemType intConst(long l) { + return SemTypes.intConst(l); + } + + @Override + public SemType mappingMemberTypeInnerVal(TypeTestContext context, SemType semType, SemType m) { + return SemTypes.mappingMemberTypeInnerVal((Context) context.getInnerContext(), semType, m); + } + + @Override + public SemType listProj(TypeTestContext context, SemType t, SemType key) { + return SemTypes.listProj((Context) context.getInnerContext(), t, key); + } + + @Override + public SemType listMemberType(TypeTestContext context, SemType t, SemType key) { + return SemTypes.listMemberType((Context) context.getInnerContext(), t, key); + } +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerTypeTestEnv.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerTypeTestEnv.java new file mode 100644 index 000000000000..bca96679f892 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerTypeTestEnv.java @@ -0,0 +1,47 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +import io.ballerina.types.Env; +import io.ballerina.types.SemType; + +import java.util.Map; + +public class CompilerTypeTestEnv implements TypeTestEnv { + + private final Env env; + + private CompilerTypeTestEnv(Env env) { + this.env = env; + } + + public static synchronized CompilerTypeTestEnv from(Env env) { + return new CompilerTypeTestEnv(env); + } + + @Override + public Map getTypeNameSemTypeMap() { + return env.getTypeNameSemTypeMap(); + } + + @Override + public void addTypeDef(String value, SemType semtype) { + env.addTypeDef(value, semtype); + } +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/ComplierTypeTestContext.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/ComplierTypeTestContext.java new file mode 100644 index 000000000000..ceafd5eddba1 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/ComplierTypeTestContext.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +import io.ballerina.types.Context; +import io.ballerina.types.SemType; + +public class ComplierTypeTestContext implements TypeTestContext { + + private final Context context; + private final TypeTestEnv env; + + private ComplierTypeTestContext(Context context) { + this.context = context; + env = CompilerTypeTestEnv.from(context.env); + } + + public static synchronized ComplierTypeTestContext from(Context context) { + return new ComplierTypeTestContext(context); + } + + @Override + public TypeTestEnv getEnv() { + return env; + } + + @Override + public Object getInnerEnv() { + return context.env; + } + + @Override + public Object getInnerContext() { + return context; + } +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeSemTypeResolver.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeSemTypeResolver.java new file mode 100644 index 000000000000..d2cdad6b54c8 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeSemTypeResolver.java @@ -0,0 +1,246 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.SemType; +import org.ballerinalang.model.tree.NodeKind; +import org.ballerinalang.model.types.TypeKind; +import org.wso2.ballerinalang.compiler.tree.BLangNode; +import org.wso2.ballerinalang.compiler.tree.BLangTypeDefinition; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangConstant; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangLiteral; +import org.wso2.ballerinalang.compiler.tree.types.BLangBuiltInRefTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangFiniteTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangIntersectionTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangType; +import org.wso2.ballerinalang.compiler.tree.types.BLangUnionTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangUserDefinedType; +import org.wso2.ballerinalang.compiler.tree.types.BLangValueType; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; + +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED16_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED16_MIN_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED32_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED32_MIN_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED8_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.SIGNED8_MIN_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED16_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED32_MAX_VALUE; +import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED8_MAX_VALUE; + +class RuntimeSemTypeResolver extends SemTypeResolver { + + Map attachedSemType = new HashMap<>(); + Map semTypeMemo = new HashMap<>(); + + @Override + public void resolveTypeDefn(TypeTestContext cx, Map modTable, + BLangTypeDefinition typeDefinition) { + resolveTypeDefnRec(cx, modTable, typeDefinition, 0); + } + + @Override + public void resolveConstant(TypeTestContext cx, Map modTable, BLangConstant constant) { + SemType semtype = evaluateConst(constant); + attachToBType(constant.typeNode, semtype); + cx.getEnv().addTypeDef(constant.name.value, semtype); + } + + private SemType resolveTypeDefnRec(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth) { + SemType memo = semTypeMemo.get(defn); + if (memo != null) { + return memo; + } + if (depth == defn.semCycleDepth) { + throw new IllegalStateException("cyclic type definition: " + defn.name.value); + } + + defn.semCycleDepth = depth; + SemType s = resolveTypeDesc(cx, mod, defn, depth, defn.typeNode); + attachToBType(defn.getTypeNode(), s); + if (!semTypeMemo.containsKey(defn)) { + semTypeMemo.put(defn, s); + defn.semCycleDepth = -1; + cx.getEnv().addTypeDef(defn.name.value, s); + return s; + } else { + return s; + } + } + + private SemType resolveTypeDesc(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, BLangType td) { + if (td == null) { + return null; + } + return switch (td.getKind()) { + case VALUE_TYPE -> resolveTypeDesc(cx, (BLangValueType) td); + case BUILT_IN_REF_TYPE -> resolveTypeDesc((BLangBuiltInRefTypeNode) td); + case INTERSECTION_TYPE_NODE -> resolveTypeDesc(cx, (BLangIntersectionTypeNode) td, mod, depth, defn); + case UNION_TYPE_NODE -> resolveTypeDesc(cx, (BLangUnionTypeNode) td, mod, depth, defn); + case USER_DEFINED_TYPE -> resolveTypeDesc(cx, (BLangUserDefinedType) td, mod, depth); + case FINITE_TYPE_NODE -> resolveSingletonType((BLangFiniteTypeNode) td); + default -> throw new UnsupportedOperationException("type not implemented: " + td.getKind()); + }; + } + + private SemType resolveSingletonType(BLangFiniteTypeNode td) { + return td.valueSpace.stream().map(each -> (BLangLiteral) each) + .map(literal -> resolveSingletonType(literal.value, literal.getDeterminedType().getKind()).get()) + .reduce(Builder.neverType(), Core::union); + } + + private Optional resolveSingletonType(Object value, TypeKind targetTypeKind) { + return switch (targetTypeKind) { + case NIL -> Optional.of(Builder.nilType()); + case BOOLEAN -> Optional.of(Builder.booleanConst((Boolean) value)); + case INT, BYTE -> { + assert !(value instanceof Byte); + yield Optional.of(Builder.intConst(((Number) value).longValue())); + } + case FLOAT -> { + double doubleVal; + if (value instanceof Long longValue) { + doubleVal = longValue.doubleValue(); + } else if (value instanceof Double doubleValue) { + doubleVal = doubleValue; + } else { + // literal value will be a string if it wasn't within the bounds of what is supported by Java Long + // or Double when it was parsed in BLangNodeBuilder. + try { + doubleVal = Double.parseDouble((String) value); + } catch (NumberFormatException e) { + yield Optional.empty(); + } + } + yield Optional.of(Builder.floatConst(doubleVal)); + } + case DECIMAL -> { + String repr = (String) value; + if (repr.contains("d") || repr.contains("D")) { + repr = repr.substring(0, repr.length() - 1); + } + yield Optional.of(Builder.decimalConst(new BigDecimal(repr))); + } + case STRING -> Optional.of(Builder.stringConst((String) value)); + default -> Optional.empty(); + }; + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangUnionTypeNode td, Map mod, + int depth, BLangTypeDefinition defn) { + + Iterator iterator = td.memberTypeNodes.iterator(); + SemType res = resolveTypeDesc(cx, mod, defn, depth, iterator.next()); + while (iterator.hasNext()) { + res = Core.union(res, resolveTypeDesc(cx, mod, defn, depth, iterator.next())); + } + return res; + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangUserDefinedType td, Map mod, + int depth) { + String name = td.typeName.value; + // Need to replace this with a real package lookup + if (td.pkgAlias.value.equals("int")) { + return resolveIntSubtype(name); + } else if (td.pkgAlias.value.equals("string") && name.equals("Char")) { + return Builder.charType(); + } + + BLangNode moduleLevelDef = mod.get(name); + if (moduleLevelDef == null) { + throw new IllegalStateException("unknown type: " + name); + } + + if (moduleLevelDef.getKind() == NodeKind.TYPE_DEFINITION) { + return resolveTypeDefnRec(cx, mod, (BLangTypeDefinition) moduleLevelDef, depth); + } else if (moduleLevelDef.getKind() == NodeKind.CONSTANT) { + BLangConstant constant = (BLangConstant) moduleLevelDef; + return resolveTypeDefnRec(cx, mod, constant.associatedTypeDefinition, depth); + } else { + throw new UnsupportedOperationException("constants and class defns not implemented"); + } + } + + private SemType resolveIntSubtype(String name) { + // TODO: support MAX_VALUE + return switch (name) { + case "Signed8" -> Builder.intRange(SIGNED8_MIN_VALUE, SIGNED8_MAX_VALUE); + case "Signed16" -> Builder.intRange(SIGNED16_MIN_VALUE, SIGNED16_MAX_VALUE); + case "Signed32" -> Builder.intRange(SIGNED32_MIN_VALUE, SIGNED32_MAX_VALUE); + case "Unsigned8" -> Builder.intRange(0, UNSIGNED8_MAX_VALUE); + case "Unsigned16" -> Builder.intRange(0, UNSIGNED16_MAX_VALUE); + case "Unsigned32" -> Builder.intRange(0, UNSIGNED32_MAX_VALUE); + default -> throw new UnsupportedOperationException("Unknown int subtype: " + name); + }; + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangIntersectionTypeNode td, + Map mod, int depth, BLangTypeDefinition defn) { + Iterator iterator = td.constituentTypeNodes.iterator(); + SemType res = resolveTypeDesc(cx, mod, defn, depth, iterator.next()); + while (iterator.hasNext()) { + res = Core.intersect(res, resolveTypeDesc(cx, mod, defn, depth, iterator.next())); + } + return res; + } + + private SemType resolveTypeDesc(BLangBuiltInRefTypeNode td) { + return switch (td.typeKind) { + case NEVER -> Builder.neverType(); + default -> throw new UnsupportedOperationException("Built-in ref type not implemented: " + td.typeKind); + }; + } + + private SemType resolveTypeDesc(TypeTestContext cx, BLangValueType td) { + return switch (td.typeKind) { + case NIL -> Builder.nilType(); + case BOOLEAN -> Builder.booleanType(); + case BYTE -> Builder.intRange(0, UNSIGNED8_MAX_VALUE); + case INT -> Builder.intType(); + case FLOAT -> Builder.floatType(); + case DECIMAL -> Builder.decimalType(); + case STRING -> Builder.stringType(); + default -> throw new IllegalStateException("Unknown type: " + td); + }; + } + + private SemType evaluateConst(BLangConstant constant) { + return switch (constant.symbol.value.type.getKind()) { + case INT -> Builder.intConst((long) constant.symbol.value.value); + case BOOLEAN -> Builder.booleanConst((boolean) constant.symbol.value.value); + case STRING -> Builder.stringConst((String) constant.symbol.value.value); + case FLOAT -> Builder.floatConst((double) constant.symbol.value.value); + default -> throw new UnsupportedOperationException("Expression type not implemented for const semtype"); + }; + } + + private void attachToBType(BLangType bType, SemType semType) { + attachedSemType.put(bType, semType); + } +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestAPI.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestAPI.java new file mode 100644 index 000000000000..e0c0e715868f --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestAPI.java @@ -0,0 +1,80 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.SemType; + +public class RuntimeTypeTestAPI implements TypeTestAPI { + + private static final RuntimeTypeTestAPI INSTANCE = new RuntimeTypeTestAPI(); + + private RuntimeTypeTestAPI() { + } + + public static RuntimeTypeTestAPI getInstance() { + return INSTANCE; + } + + @Override + public boolean isSubtype(TypeTestContext cx, SemType t1, SemType t2) { + return Core.isSubType(form(cx), t1, t2); + } + + private static Context form(TypeTestContext cx) { + return (Context) cx.getInnerContext(); + } + + @Override + public boolean isSubtypeSimple(SemType t1, SemType t2) { + return Core.isSubtypeSimple(t1, t2); + } + + @Override + public boolean isListType(SemType t) { + throw new IllegalArgumentException("list type not implemented"); + } + + @Override + public boolean isMapType(SemType t) { + throw new IllegalArgumentException("map type not implemented"); + } + + @Override + public SemType intConst(long l) { + return Builder.intConst(l); + } + + @Override + public SemType mappingMemberTypeInnerVal(TypeTestContext context, SemType semType, SemType m) { + throw new IllegalArgumentException("mapping member type inner val not implemented"); + } + + @Override + public SemType listProj(TypeTestContext context, SemType t, SemType key) { + throw new IllegalArgumentException("list proj not implemented"); + } + + @Override + public SemType listMemberType(TypeTestContext context, SemType t, SemType key) { + throw new IllegalArgumentException("list member type not implemented"); + } +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestContext.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestContext.java new file mode 100644 index 000000000000..dba9c3cc2a4f --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestContext.java @@ -0,0 +1,50 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; + +public final class RuntimeTypeTestContext implements TypeTestContext { + + private final TypeTestEnv env; + + private RuntimeTypeTestContext(TypeTestEnv env) { + this.env = env; + } + + public static synchronized RuntimeTypeTestContext from(TypeTestEnv env) { + return new RuntimeTypeTestContext(env); + } + + @Override + public TypeTestEnv getEnv() { + return env; + } + + @Override + public Object getInnerEnv() { + throw new IllegalStateException("not implemented"); + } + + @Override + public Object getInnerContext() { + return new Context(); + } +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestEnv.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestEnv.java new file mode 100644 index 000000000000..ed7605804b82 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestEnv.java @@ -0,0 +1,47 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.HashMap; +import java.util.Map; + +class RuntimeTypeTestEnv implements TypeTestEnv { + + private final Map typeMap = new HashMap<>(); + + private RuntimeTypeTestEnv() { + + } + + public static synchronized RuntimeTypeTestEnv from() { + return new RuntimeTypeTestEnv(); + } + + @Override + public Map getTypeNameSemTypeMap() { + return typeMap; + } + + @Override + public void addTypeDef(String value, SemType semtype) { + typeMap.put(value, semtype); + } +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeAssertionTransformer.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeAssertionTransformer.java index c86e7403fc30..b43a4fa84f33 100644 --- a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeAssertionTransformer.java +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeAssertionTransformer.java @@ -26,14 +26,8 @@ import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.SyntaxTree; import io.ballerina.compiler.syntax.tree.Token; -import io.ballerina.types.Context; -import io.ballerina.types.Env; -import io.ballerina.types.PredefinedType; -import io.ballerina.types.SemType; -import io.ballerina.types.SemTypes; import org.testng.Assert; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -42,35 +36,45 @@ /** * Extract semtype assertions in the below form. + * @param which semtype (runtime or compiler) used in the assertions. * // @type A < B * // @type B = C * // @type c <> D - * + * @param Actual semtype definition on which assertion is defined on (runtime or compile time) * @since 3.0.0 */ -public class SemTypeAssertionTransformer extends NodeVisitor { +public final class SemTypeAssertionTransformer extends NodeVisitor { + private final String fileName; private final SyntaxTree syntaxTree; - private final Env semtypeEnv; - private final Context context; + private final TypeTestEnv semtypeEnv; + private final TypeTestContext context; private final List list; + private final TypeTestAPI semtypeAPI; - private SemTypeAssertionTransformer(String fileName, SyntaxTree syntaxTree, Env semtypeEnv) { + private SemTypeAssertionTransformer(String fileName, SyntaxTree syntaxTree, TypeTestEnv semtypeEnv, + TypeTestContext context, TypeTestAPI semtypeAPI) { this.fileName = fileName; this.syntaxTree = syntaxTree; this.semtypeEnv = semtypeEnv; - this.context = Context.from(semtypeEnv); + this.context = context; list = new ArrayList<>(); + this.semtypeAPI = semtypeAPI; } - public static List getTypeAssertionsFrom(String fileName, SyntaxTree syntaxTree, Env semtypeEnv) { - final SemTypeAssertionTransformer t = new SemTypeAssertionTransformer(fileName, syntaxTree, semtypeEnv); + public static List> getTypeAssertionsFrom(String fileName, + SyntaxTree syntaxTree, + TypeTestEnv semtypeEnv, + TypeTestContext context, + TypeTestAPI api) { + final SemTypeAssertionTransformer t = + new SemTypeAssertionTransformer<>(fileName, syntaxTree, semtypeEnv, context, api); return t.getTypeAssertions(); } - private List getTypeAssertions() { + private List> getTypeAssertions() { syntaxTree.rootNode().accept(this); - List assertions = new ArrayList<>(); + List> assertions = new ArrayList<>(); for (String str : list) { String[] parts = splitAssertion(str); if (parts == null) { @@ -80,7 +84,7 @@ private List getTypeAssertions() { RelKind kind = RelKind.fromString(parts[1], str); SemType rhs = toSemType(parts[2]); String text = parts[0] + " " + parts[1] + " " + parts[2]; - assertions.add(new TypeAssertion(this.context, this.fileName, lhs, rhs, kind, text)); + assertions.add(new TypeAssertion<>(this.context, this.fileName, lhs, rhs, kind, text)); } return assertions; } @@ -101,27 +105,27 @@ private SemType toSemType(String typeExpr) { String memberAccessExpr = typeExpr.substring(leftBracketPos + 1, rightBracketPos); SemType type = typeNameSemTypeMap.get(typeRef); - if (SemTypes.isSubtypeSimple(type, PredefinedType.LIST)) { + if (semtypeAPI.isListType(type)) { SemType m; try { long l = Long.parseLong(memberAccessExpr); - m = SemTypes.intConst(l); + m = semtypeAPI.intConst(l); } catch (Exception e) { // parsing number failed, access must be a type-reference m = typeNameSemTypeMap.get(memberAccessExpr); } return listProj(context, type, m); - } else if (SemTypes.isSubtypeSimple(type, PredefinedType.MAPPING)) { + } else if (semtypeAPI.isMapType(type)) { SemType m = typeNameSemTypeMap.get(memberAccessExpr); - return SemTypes.mappingMemberTypeInnerVal(context, type, m); + return semtypeAPI.mappingMemberTypeInnerVal(context, type, m); } throw new IllegalStateException("Unsupported type test: " + typeExpr); } - private SemType listProj(Context context, SemType t, SemType m) { - SemType s1 = SemTypes.listProj(context, t, m); - SemType s2 = SemTypes.listMemberType(context, t, m); - if (!SemTypes.isSubtype(context, s1, s2)) { + private SemType listProj(TypeTestContext context, SemType t, SemType m) { + SemType s1 = semtypeAPI.listProj(context, t, m); + SemType s2 = semtypeAPI.listMemberType(context, t, m); + if (!semtypeAPI.isSubtype(context, s1, s2)) { Assert.fail("listProj result is not a subtype of listMemberType"); } return s1; @@ -189,32 +193,6 @@ public void visit(ModulePartNode modulePartNode) { modulePartNode.eofToken().accept(this); } - /** - * Subtype test. - * - * @param context Type context under which {@code SemTypes} were defined. - * @param fileName Name of the file in which types were defined in. - * @param lhs Resolved {@code SemType} for the Left-hand side of the subtype test. - * @param rhs Resolved {@code SemType} for the Right-hand side of the subtype test. - * @param kind Relationship between the two types. - * @param text Text that will be shown in case of assertion failure. - * @since 3.0.0 - */ - record TypeAssertion(Context context, String fileName, SemType lhs, SemType rhs, RelKind kind, String text) { - - TypeAssertion { - if (kind != null) { - assert lhs != null; - assert rhs != null; - } - } - - @Override - public String toString() { - return Paths.get(fileName).getFileName().toString() + ": " + text; - } - } - /** * Relationship to be asserted. * diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeResolver.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeResolver.java index 09b2127651eb..f8695377e5cf 100644 --- a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeResolver.java +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeResolver.java @@ -15,77 +15,24 @@ * specific language governing permissions and limitations * under the License. */ + package io.ballerina.semtype.port.test; -import io.ballerina.types.CellAtomicType; -import io.ballerina.types.Context; -import io.ballerina.types.Core; -import io.ballerina.types.Definition; -import io.ballerina.types.PredefinedType; -import io.ballerina.types.SemType; -import io.ballerina.types.SemTypes; -import io.ballerina.types.definition.Field; -import io.ballerina.types.definition.FunctionDefinition; -import io.ballerina.types.definition.FunctionQualifiers; -import io.ballerina.types.definition.ListDefinition; -import io.ballerina.types.definition.MappingDefinition; -import io.ballerina.types.definition.Member; -import io.ballerina.types.definition.ObjectDefinition; -import io.ballerina.types.definition.ObjectQualifiers; -import io.ballerina.types.definition.StreamDefinition; -import io.ballerina.types.subtypedata.FloatSubtype; -import org.ballerinalang.model.elements.Flag; import org.ballerinalang.model.tree.NodeKind; -import org.ballerinalang.model.tree.types.ArrayTypeNode; -import org.ballerinalang.model.tree.types.TypeNode; -import org.ballerinalang.model.types.TypeKind; -import org.wso2.ballerinalang.compiler.tree.BLangFunction; import org.wso2.ballerinalang.compiler.tree.BLangNode; -import org.wso2.ballerinalang.compiler.tree.BLangSimpleVariable; import org.wso2.ballerinalang.compiler.tree.BLangTypeDefinition; import org.wso2.ballerinalang.compiler.tree.expressions.BLangConstant; -import org.wso2.ballerinalang.compiler.tree.expressions.BLangExpression; -import org.wso2.ballerinalang.compiler.tree.expressions.BLangLiteral; -import org.wso2.ballerinalang.compiler.tree.expressions.BLangSimpleVarRef; -import org.wso2.ballerinalang.compiler.tree.types.BLangArrayType; -import org.wso2.ballerinalang.compiler.tree.types.BLangBuiltInRefTypeNode; -import org.wso2.ballerinalang.compiler.tree.types.BLangConstrainedType; -import org.wso2.ballerinalang.compiler.tree.types.BLangErrorType; -import org.wso2.ballerinalang.compiler.tree.types.BLangFiniteTypeNode; -import org.wso2.ballerinalang.compiler.tree.types.BLangFunctionTypeNode; -import org.wso2.ballerinalang.compiler.tree.types.BLangIntersectionTypeNode; -import org.wso2.ballerinalang.compiler.tree.types.BLangObjectTypeNode; -import org.wso2.ballerinalang.compiler.tree.types.BLangRecordTypeNode; -import org.wso2.ballerinalang.compiler.tree.types.BLangStreamType; -import org.wso2.ballerinalang.compiler.tree.types.BLangTableTypeNode; -import org.wso2.ballerinalang.compiler.tree.types.BLangTupleTypeNode; -import org.wso2.ballerinalang.compiler.tree.types.BLangType; -import org.wso2.ballerinalang.compiler.tree.types.BLangUnionTypeNode; -import org.wso2.ballerinalang.compiler.tree.types.BLangUserDefinedType; -import org.wso2.ballerinalang.compiler.tree.types.BLangValueType; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; import static org.wso2.ballerinalang.compiler.semantics.analyzer.SymbolEnter.getTypeOrClassName; -/** - * Resolves sem-types for module definitions. - * - * @since 2201.10.0 - */ -public class SemTypeResolver { +public abstract class SemTypeResolver { - private final Map attachedDefinitions = new HashMap<>(); - - void defineSemTypes(List moduleDefs, Context cx) { + public void defineSemTypes(List moduleDefs, TypeTestContext cx) { Map modTable = new LinkedHashMap<>(); for (BLangNode typeAndClassDef : moduleDefs) { modTable.put(getTypeOrClassName(typeAndClassDef), typeAndClassDef); @@ -99,595 +46,14 @@ void defineSemTypes(List moduleDefs, Context cx) { resolveConstant(cx, modTable, (BLangConstant) def); } else { BLangTypeDefinition typeDefinition = (BLangTypeDefinition) def; - resolveTypeDefn(cx, modTable, typeDefinition, 0); - } - } - } - - private void resolveConstant(Context cx, Map modTable, BLangConstant constant) { - SemType semtype = evaluateConst(constant); - addSemTypeBType(constant.getTypeNode(), semtype); - cx.env.addTypeDef(constant.name.value, semtype); - } - - private SemType evaluateConst(BLangConstant constant) { - switch (constant.symbol.value.type.getKind()) { - case INT: - return SemTypes.intConst((long) constant.symbol.value.value); - case BOOLEAN: - return SemTypes.booleanConst((boolean) constant.symbol.value.value); - case STRING: - return SemTypes.stringConst((String) constant.symbol.value.value); - case FLOAT: - return SemTypes.floatConst((double) constant.symbol.value.value); - default: - throw new UnsupportedOperationException("Expression type not implemented for const semtype"); - } - } - - private SemType resolveTypeDefn(Context cx, Map mod, BLangTypeDefinition defn, int depth) { - if (defn.semType != null) { - return defn.semType; - } - - if (depth == defn.semCycleDepth) { - throw new IllegalStateException("cyclic type definition: " + defn.name.value); - } - defn.semCycleDepth = depth; - SemType s = resolveTypeDesc(cx, mod, defn, depth, defn.typeNode); - addSemTypeBType(defn.getTypeNode(), s); - if (defn.semType == null) { - defn.semType = s; - defn.semCycleDepth = -1; - cx.env.addTypeDef(defn.name.value, s); - return s; - } else { - return s; - } - } - - private void addSemTypeBType(BLangType typeNode, SemType semType) { - if (typeNode != null) { - typeNode.getBType().semType(semType); - } - } - - public SemType resolveTypeDesc(Context cx, Map mod, BLangTypeDefinition defn, int depth, - TypeNode td) { - if (td == null) { - return null; - } - switch (td.getKind()) { - case VALUE_TYPE: - return resolveTypeDesc(cx, (BLangValueType) td); - case BUILT_IN_REF_TYPE: - return resolveTypeDesc(cx, (BLangBuiltInRefTypeNode) td); - case RECORD_TYPE: - return resolveTypeDesc(cx, (BLangRecordTypeNode) td, mod, depth, defn); - case CONSTRAINED_TYPE: // map and typedesc - return resolveTypeDesc(cx, (BLangConstrainedType) td, mod, depth, defn); - case UNION_TYPE_NODE: - return resolveTypeDesc(cx, (BLangUnionTypeNode) td, mod, depth, defn); - case INTERSECTION_TYPE_NODE: - return resolveTypeDesc(cx, (BLangIntersectionTypeNode) td, mod, depth, defn); - case USER_DEFINED_TYPE: - return resolveTypeDesc(cx, (BLangUserDefinedType) td, mod, depth); - case FINITE_TYPE_NODE: - return resolveSingletonType((BLangFiniteTypeNode) td); - case ARRAY_TYPE: - return resolveTypeDesc(cx, mod, defn, depth, (BLangArrayType) td); - case TUPLE_TYPE_NODE: - return resolveTypeDesc(cx, mod, defn, depth, (BLangTupleTypeNode) td); - case FUNCTION_TYPE: - return resolveTypeDesc(cx, mod, defn, depth, (BLangFunctionTypeNode) td); - case TABLE_TYPE: - return resolveTypeDesc(cx, mod, defn, depth, (BLangTableTypeNode) td); - case ERROR_TYPE: - return resolveTypeDesc(cx, mod, defn, depth, (BLangErrorType) td); - case OBJECT_TYPE: - return resolveTypeDesc(cx, mod, defn, depth, (BLangObjectTypeNode) td); - case STREAM_TYPE: - return resolveTypeDesc(cx, mod, defn, depth, (BLangStreamType) td); - default: - throw new UnsupportedOperationException("type not implemented: " + td.getKind()); - } - } - - private SemType resolveTypeDesc(Context cx, Map mod, BLangTypeDefinition defn, int depth, - BLangObjectTypeNode td) { - SemType innerType = resolveNonDistinctObject(cx, mod, defn, depth, td); - if (td.flagSet.contains(Flag.DISTINCT)) { - return getDistinctObjectType(cx, innerType); - } - return innerType; - } - - private static SemType getDistinctObjectType(Context cx, SemType innerType) { - return Core.intersect(SemTypes.objectDistinct(cx.env.distinctAtomCountGetAndIncrement()), innerType); - } - - private SemType resolveNonDistinctObject(Context cx, Map mod, BLangTypeDefinition defn, - int depth, - BLangObjectTypeNode td) { - if (td.defn != null) { - return td.defn.getSemType(cx.env); - } - ObjectDefinition od = new ObjectDefinition(); - Stream fieldStream = td.fields.stream().map(field -> { - Set flags = field.flagSet; - Member.Visibility visibility = flags.contains(Flag.PUBLIC) ? Member.Visibility.Public : - Member.Visibility.Private; - SemType ty = resolveTypeDesc(cx, mod, defn, depth + 1, field.typeNode); - return new Member(field.name.value, ty, Member.Kind.Field, visibility, flags.contains(Flag.READONLY)); - }); - Stream methodStream = td.getFunctions().stream().map(method -> { - Member.Visibility visibility = method.flagSet.contains(Flag.PUBLIC) ? Member.Visibility.Public : - Member.Visibility.Private; - SemType ty = resolveTypeDesc(cx, mod, defn, depth + 1, method); - return new Member(method.name.value, ty, Member.Kind.Method, visibility, true); - }); - td.defn = od; - List members = Stream.concat(fieldStream, methodStream).toList(); - ObjectQualifiers qualifiers = getQualifiers(td); - return od.define(cx.env, qualifiers, members); - } - - private static ObjectQualifiers getQualifiers(BLangObjectTypeNode td) { - Set flags = td.symbol.getFlags(); - ObjectQualifiers.NetworkQualifier networkQualifier; - assert !(flags.contains(Flag.CLIENT) && flags.contains(Flag.SERVICE)) : - "object can't be both client and service"; - if (flags.contains(Flag.CLIENT)) { - networkQualifier = ObjectQualifiers.NetworkQualifier.Client; - } else if (flags.contains(Flag.SERVICE)) { - networkQualifier = ObjectQualifiers.NetworkQualifier.Service; - } else { - networkQualifier = ObjectQualifiers.NetworkQualifier.None; - } - return new ObjectQualifiers(flags.contains(Flag.ISOLATED), flags.contains(Flag.READONLY), networkQualifier); - } - - // TODO: should we make definition part of BLangFunction as well? - private SemType resolveTypeDesc(Context cx, Map mod, BLangTypeDefinition defn, int depth, - BLangFunction functionType) { - Definition attached = attachedDefinitions.get(functionType); - if (attached != null) { - return attached.getSemType(cx.env); - } - FunctionDefinition fd = new FunctionDefinition(); - attachedDefinitions.put(functionType, fd); - List params = functionType.getParameters().stream() - .map(paramVar -> resolveTypeDesc(cx, mod, defn, depth + 1, paramVar.typeNode)).toList(); - SemType rest; - if (functionType.getRestParameters() == null) { - rest = PredefinedType.NEVER; - } else { - ArrayTypeNode arrayType = (ArrayTypeNode) functionType.getRestParameters().getTypeNode(); - rest = resolveTypeDesc(cx, mod, defn, depth + 1, arrayType.getElementType()); - } - SemType returnType = functionType.getReturnTypeNode() != null ? - resolveTypeDesc(cx, mod, defn, depth + 1, functionType.getReturnTypeNode()) : PredefinedType.NIL; - ListDefinition paramListDefinition = new ListDefinition(); - FunctionQualifiers qualifiers = FunctionQualifiers.from(cx.env, functionType.flagSet.contains(Flag.ISOLATED), - functionType.flagSet.contains(Flag.TRANSACTIONAL)); - return fd.define(cx.env, paramListDefinition.defineListTypeWrapped(cx.env, params, params.size(), rest, - CellAtomicType.CellMutability.CELL_MUT_NONE), returnType, qualifiers); - } - - private SemType resolveTypeDesc(Context cx, Map mod, BLangTypeDefinition defn, int depth, - BLangFunctionTypeNode td) { - if (isFunctionTop(td)) { - if (td.flagSet.contains(Flag.ISOLATED) || td.flagSet.contains(Flag.TRANSACTIONAL)) { - FunctionQualifiers qualifiers = FunctionQualifiers.from(cx.env, td.flagSet.contains(Flag.ISOLATED), - td.flagSet.contains(Flag.TRANSACTIONAL)); - // I think param type here is wrong. It should be the intersection of all list types, but I think - // never is close enough - return new FunctionDefinition().define(cx.env, PredefinedType.NEVER, PredefinedType.VAL, qualifiers); - } - return PredefinedType.FUNCTION; - } - if (td.defn != null) { - return td.defn.getSemType(cx.env); - } - FunctionDefinition fd = new FunctionDefinition(); - td.defn = fd; - List params = - td.params.stream().map(param -> resolveTypeDesc(cx, mod, defn, depth + 1, param.typeNode)) - .toList(); - SemType rest; - if (td.restParam == null) { - rest = PredefinedType.NEVER; - } else { - BLangArrayType restArrayType = (BLangArrayType) td.restParam.typeNode; - rest = resolveTypeDesc(cx, mod, defn, depth + 1, restArrayType.elemtype); - } - SemType returnType = td.returnTypeNode != null ? resolveTypeDesc(cx, mod, defn, depth + 1, td.returnTypeNode) : - PredefinedType.NIL; - ListDefinition paramListDefinition = new ListDefinition(); - FunctionQualifiers qualifiers = FunctionQualifiers.from(cx.env, td.flagSet.contains(Flag.ISOLATED), - td.flagSet.contains(Flag.TRANSACTIONAL)); - return fd.define(cx.env, paramListDefinition.defineListTypeWrapped(cx.env, params, params.size(), rest, - CellAtomicType.CellMutability.CELL_MUT_NONE), returnType, qualifiers); - } - - private boolean isFunctionTop(BLangFunctionTypeNode td) { - return td.params.isEmpty() && td.restParam == null && td.returnTypeNode == null; - } - - private SemType resolveTypeDesc(Context cx, Map mod, BLangTypeDefinition defn, int depth, - BLangArrayType td) { - if (td.defn != null) { - return td.defn.getSemType(cx.env); - } - ListDefinition ld = new ListDefinition(); - td.defn = ld; - - int dimensions = td.dimensions; - SemType accum = resolveTypeDesc(cx, mod, defn, depth + 1, td.elemtype); - for (int i = 0; i < dimensions; i++) { - int size = from(mod, td.sizes.get(i)); - if (i == dimensions - 1) { - accum = resolveListInner(cx, ld, size, accum); - } else { - accum = resolveListInner(cx, size, accum); - } - } - return accum; - } - - private static int from(Map mod, BLangNode expr) { - if (expr instanceof BLangLiteral literal) { - return listSize((Number) literal.value); - } else if (expr instanceof BLangSimpleVarRef varRef) { - String varName = varRef.variableName.value; - return from(mod, mod.get(varName)); - } else if (expr instanceof BLangConstant constant) { - return listSize((Number) constant.symbol.value.value); - } - throw new UnsupportedOperationException("Unsupported expr kind " + expr.getKind()); - } - - private static int listSize(Number size) { - if (size.longValue() > Integer.MAX_VALUE) { - throw new IllegalArgumentException("list sizes greater than " + Integer.MAX_VALUE + " not yet supported"); - } - return size.intValue(); - } - - private SemType resolveListInner(Context cx, int size, SemType eType) { - ListDefinition ld = new ListDefinition(); - return resolveListInner(cx, ld, size, eType); - } - - private static SemType resolveListInner(Context cx, ListDefinition ld, int size, SemType eType) { - if (size != -1) { - return ld.defineListTypeWrapped(cx.env, List.of(eType), Math.abs(size), PredefinedType.NEVER, - CellAtomicType.CellMutability.CELL_MUT_LIMITED); - } else { - return ld.defineListTypeWrapped(cx.env, List.of(), 0, eType, - CellAtomicType.CellMutability.CELL_MUT_LIMITED); - } - } - - private SemType resolveTypeDesc(Context cx, Map mod, BLangTypeDefinition defn, int depth, - BLangTupleTypeNode td) { - if (td.defn != null) { - return td.defn.getSemType(cx.env); - } - ListDefinition ld = new ListDefinition(); - td.defn = ld; - List memberSemTypes = - td.members.stream().map(member -> resolveTypeDesc(cx, mod, defn, depth + 1, member.typeNode)) - .toList(); - SemType rest = td.restParamType != null ? resolveTypeDesc(cx, mod, defn, depth + 1, td.restParamType) : - PredefinedType.NEVER; - return ld.defineListTypeWrapped(cx.env, memberSemTypes, memberSemTypes.size(), rest); - } - - private SemType resolveTypeDesc(Context cx, BLangValueType td) { - switch (td.typeKind) { - case NIL: - return PredefinedType.NIL; - case BOOLEAN: - return PredefinedType.BOOLEAN; - case BYTE: - return PredefinedType.BYTE; - case INT: - return PredefinedType.INT; - case FLOAT: - return PredefinedType.FLOAT; - case DECIMAL: - return PredefinedType.DECIMAL; - case STRING: - return PredefinedType.STRING; - case TYPEDESC: - return PredefinedType.TYPEDESC; - case ERROR: - return PredefinedType.ERROR; - case HANDLE: - return PredefinedType.HANDLE; - case XML: - return PredefinedType.XML; - case ANY: - return PredefinedType.ANY; - case READONLY: - return PredefinedType.VAL_READONLY; - case ANYDATA: - return Core.createAnydata(cx); - case JSON: - return Core.createJson(cx); - default: - throw new IllegalStateException("Unknown type: " + td); - } - } - - private SemType resolveTypeDesc(Context cx, BLangBuiltInRefTypeNode td) { - return switch (td.typeKind) { - case NEVER -> PredefinedType.NEVER; - case XML -> PredefinedType.XML; - case JSON -> Core.createJson(cx); - default -> throw new UnsupportedOperationException("Built-in ref type not implemented: " + td.typeKind); - }; - } - - private SemType resolveTypeDesc(Context cx, BLangConstrainedType td, Map mod, - int depth, BLangTypeDefinition defn) { - TypeKind typeKind = ((BLangBuiltInRefTypeNode) td.getType()).getTypeKind(); - return switch (typeKind) { - case MAP -> resolveMapTypeDesc(td, cx, mod, depth, defn); - case XML -> resolveXmlTypeDesc(td, cx, mod, depth, defn); - case FUTURE -> resolveFutureTypeDesc(td, cx, mod, depth, defn); - case TYPEDESC -> resolveTypedescTypeDesc(td, cx, mod, depth, defn); - default -> throw new IllegalStateException("Unexpected constrained type: " + typeKind); - }; - } - - private SemType resolveMapTypeDesc(BLangConstrainedType td, Context cx, Map mod, int depth, - BLangTypeDefinition typeDefinition) { - if (td.defn != null) { - return td.defn.getSemType(cx.env); - } - - MappingDefinition d = new MappingDefinition(); - td.defn = d; - - SemType rest = resolveTypeDesc(cx, mod, typeDefinition, depth + 1, td.constraint); - return d.defineMappingTypeWrapped(cx.env, Collections.emptyList(), rest == null ? PredefinedType.NEVER : rest); - } - - private SemType resolveXmlTypeDesc(BLangConstrainedType td, Context cx, Map mod, int depth, - BLangTypeDefinition defn) { - SemType constraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); - return SemTypes.xmlSequence(constraint); - } - - private SemType resolveFutureTypeDesc(BLangConstrainedType td, Context cx, Map mod, int depth, - BLangTypeDefinition defn) { - SemType constraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); - return SemTypes.futureContaining(cx.env, constraint); - } - - private SemType resolveTypedescTypeDesc(BLangConstrainedType td, Context cx, Map mod, int depth, - BLangTypeDefinition defn) { - SemType constraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); - return SemTypes.typedescContaining(cx.env, constraint); - } - - private SemType resolveTypeDesc(Context cx, BLangRecordTypeNode td, Map mod, int depth, - BLangTypeDefinition typeDefinition) { - if (td.defn != null) { - return td.defn.getSemType(cx.env); - } - - MappingDefinition d = new MappingDefinition(); - td.defn = d; - - List fields = new ArrayList<>(); - for (BLangSimpleVariable field : td.fields) { - SemType ty = resolveTypeDesc(cx, mod, typeDefinition, depth + 1, field.typeNode); - if (Core.isNever(ty)) { - throw new IllegalStateException("record field can't be never"); - } - fields.add(Field.from(field.name.value, ty, false, field.flagSet.contains(Flag.OPTIONAL))); - } - - SemType rest; - if (!td.isSealed() && td.getRestFieldType() == null) { - rest = Core.createAnydata(cx); - } else { - rest = resolveTypeDesc(cx, mod, typeDefinition, depth + 1, td.restFieldType); - } - - return d.defineMappingTypeWrapped(cx.env, fields, rest == null ? PredefinedType.NEVER : rest); - } - - private SemType resolveTypeDesc(Context cx, BLangUnionTypeNode td, Map mod, int depth, - BLangTypeDefinition defn) { - Iterator iterator = td.memberTypeNodes.iterator(); - SemType u = resolveTypeDesc(cx, mod, defn, depth, iterator.next()); - while (iterator.hasNext()) { - u = Core.union(u, resolveTypeDesc(cx, mod, defn, depth, iterator.next())); - } - return u; - } - - private SemType resolveTypeDesc(Context cx, BLangIntersectionTypeNode td, Map mod, int depth, - BLangTypeDefinition defn) { - Iterator iterator = td.constituentTypeNodes.iterator(); - SemType i = resolveTypeDesc(cx, mod, defn, depth, iterator.next()); - while (iterator.hasNext()) { - i = Core.intersect(i, resolveTypeDesc(cx, mod, defn, depth, iterator.next())); - } - return i; - } - - private SemType resolveTypeDesc(Context cx, BLangUserDefinedType td, Map mod, int depth) { - String name = td.typeName.value; - // Need to replace this with a real package lookup - if (td.pkgAlias.value.equals("int")) { - return resolveIntSubtype(name); - } else if (td.pkgAlias.value.equals("string") && name.equals("Char")) { - return SemTypes.CHAR; - } else if (td.pkgAlias.value.equals("xml")) { - return resolveXmlSubtype(name); - } else if (td.pkgAlias.value.equals("regexp") && name.equals("RegExp")) { - return PredefinedType.REGEXP; - } - - BLangNode moduleLevelDef = mod.get(name); - if (moduleLevelDef == null) { - throw new IllegalStateException("unknown type: " + name); - } - - if (moduleLevelDef.getKind() == NodeKind.TYPE_DEFINITION) { - SemType ty = resolveTypeDefn(cx, mod, (BLangTypeDefinition) moduleLevelDef, depth); - if (td.flagSet.contains(Flag.DISTINCT)) { - return getDistinctSemType(cx, ty); + resolveTypeDefn(cx, modTable, typeDefinition); } - return ty; - } else if (moduleLevelDef.getKind() == NodeKind.CONSTANT) { - BLangConstant constant = (BLangConstant) moduleLevelDef; - return resolveTypeDefn(cx, mod, constant.associatedTypeDefinition, depth); - } else { - throw new UnsupportedOperationException("constants and class defns not implemented"); } } - private SemType getDistinctSemType(Context cx, SemType innerType) { - if (Core.isSubtypeSimple(innerType, PredefinedType.OBJECT)) { - return getDistinctObjectType(cx, innerType); - } else if (Core.isSubtypeSimple(innerType, PredefinedType.ERROR)) { - return getDistinctErrorType(cx, innerType); - } - throw new IllegalArgumentException("Distinct type not supported for: " + innerType); - } - - private SemType resolveIntSubtype(String name) { - // TODO: support MAX_VALUE - return switch (name) { - case "Signed8" -> SemTypes.SINT8; - case "Signed16" -> SemTypes.SINT16; - case "Signed32" -> SemTypes.SINT32; - case "Unsigned8" -> SemTypes.UINT8; - case "Unsigned16" -> SemTypes.UINT16; - case "Unsigned32" -> SemTypes.UINT32; - default -> throw new UnsupportedOperationException("Unknown int subtype: " + name); - }; - } - - private SemType resolveXmlSubtype(String name) { - return switch (name) { - case "Element" -> SemTypes.XML_ELEMENT; - case "Comment" -> SemTypes.XML_COMMENT; - case "Text" -> SemTypes.XML_TEXT; - case "ProcessingInstruction" -> SemTypes.XML_PI; - default -> throw new IllegalStateException("Unknown XML subtype: " + name); - }; - } - - private SemType resolveSingletonType(BLangFiniteTypeNode td) { - return resolveSingletonType(td.valueSpace); - } - - private SemType resolveSingletonType(List valueSpace) { - List types = new ArrayList<>(); - for (BLangExpression bLangExpression : valueSpace) { - types.add(resolveSingletonType((BLangLiteral) bLangExpression)); - } - - Iterator iter = types.iterator(); - SemType u = iter.next(); - while (iter.hasNext()) { - u = SemTypes.union(u, iter.next()); - } - return u; - } - - private SemType resolveSingletonType(BLangLiteral literal) { - return resolveSingletonType(literal.value, literal.getDeterminedType().getKind()); - } - - private SemType resolveSingletonType(Object value, TypeKind targetTypeKind) { - return switch (targetTypeKind) { - case NIL -> PredefinedType.NIL; - case BOOLEAN -> SemTypes.booleanConst((Boolean) value); - case INT, BYTE -> { - assert !(value instanceof Byte); - yield SemTypes.intConst(((Number) value).longValue()); - } - case FLOAT -> { - double doubleVal; - if (value instanceof Long longValue) { - doubleVal = longValue.doubleValue(); - } else if (value instanceof Double doubleValue) { - doubleVal = doubleValue; - } else { - // literal value will be a string if it wasn't within the bounds of what is supported by Java Long - // or Double when it was parsed in BLangNodeBuilder. - try { - doubleVal = Double.parseDouble((String) value); - } catch (NumberFormatException e) { - // We reach here when there is a syntax error. Mock the flow with default float value. - yield FloatSubtype.floatConst(0); - } - } - yield SemTypes.floatConst(doubleVal); - // literal value will be a string if it wasn't within the bounds of what is supported by Java Long - // or Double when it was parsed in BLangNodeBuilder. - // We reach here when there is a syntax error. Mock the flow with default float value. - } - case DECIMAL -> SemTypes.decimalConst((String) value); - case STRING -> SemTypes.stringConst((String) value); - default -> throw new UnsupportedOperationException("Finite type not implemented for: " + targetTypeKind); - }; - } - - private SemType resolveTypeDesc(Context cx, Map mod, BLangTypeDefinition defn, int depth, - BLangTableTypeNode td) { - if (td.tableKeySpecifier != null || td.tableKeyTypeConstraint != null) { - throw new UnsupportedOperationException("table key constraint not supported yet"); - } - - SemType memberType = resolveTypeDesc(cx, mod, defn, depth, td.constraint); - return SemTypes.tableContaining(cx.env, memberType); - } - - private SemType resolveTypeDesc(Context cx, Map mod, BLangTypeDefinition defn, int depth, - BLangErrorType td) { - SemType err; - if (td.detailType == null) { - err = PredefinedType.ERROR; - } else { - SemType detail = resolveTypeDesc(cx, mod, defn, depth, td.detailType); - err = SemTypes.errorDetail(detail); - } - - if (td.flagSet.contains(Flag.DISTINCT)) { - err = getDistinctErrorType(cx, err); - } - return err; - } - - private static SemType getDistinctErrorType(Context cx, SemType err) { - return Core.intersect(SemTypes.errorDistinct(cx.env.distinctAtomCountGetAndIncrement()), err); - } - - private SemType resolveTypeDesc(Context cx, Map mod, BLangTypeDefinition defn, int depth, - BLangStreamType td) { - if (td.constraint == null) { - return PredefinedType.STREAM; - } + protected abstract void resolveConstant(TypeTestContext cx, + Map modTable, BLangConstant constant); - if (td.defn != null) { - return td.defn.getSemType(cx.env); - } - - StreamDefinition d = new StreamDefinition(); - td.defn = d; - - SemType valueType = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); - SemType completionType = td.error == null ? - PredefinedType.NIL : resolveTypeDesc(cx, mod, defn, depth + 1, td.error); - return d.define(cx.env, valueType, completionType); - } + protected abstract void resolveTypeDefn(TypeTestContext cx, + Map mod, BLangTypeDefinition defn); } diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeTest.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeTest.java index 93e9219c176c..795eb6bc70ab 100644 --- a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeTest.java +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/SemTypeTest.java @@ -41,11 +41,14 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.StringJoiner; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -101,37 +104,40 @@ public Object[] typeRelTestFileNameProvider() { listAllBalFiles(dataDir, balFiles); Collections.sort(balFiles); - List tests = new ArrayList<>(); + Collection> tests = new ArrayList<>(); for (File file : balFiles) { - List assertions = getTypeAssertions(file); + TypeCheckData utils = compilerTypeResolverUtilsFromFile(file); + List> assertions = + getTypeAssertions(file, utils.resolver(), utils.context(), utils.env(), + CompilerTypeTestAPI.getInstance(), utils.pair()); tests.addAll(assertions); } return tests.toArray(); } @NotNull - private static List getTypeAssertions(File file) { + private static List> getTypeAssertions(File file, + SemTypeResolver typeResolver, + TypeTestContext typeCheckContext, + TypeTestEnv typeTestEnv, + TypeTestAPI api, + BCompileUtil.PackageSyntaxTreePair pair) { String fileName = file.getAbsolutePath(); - BCompileUtil.PackageSyntaxTreePair pair = BCompileUtil.compileSemType(fileName); BLangPackage pkgNode = pair.bLangPackage; List typeAndClassDefs = new ArrayList<>(); typeAndClassDefs.addAll(pkgNode.constants); typeAndClassDefs.addAll(pkgNode.typeDefinitions); - SemTypeResolver typeResolver = new SemTypeResolver(); - Context typeCheckContext = Context.from(pkgNode.semtypeEnv); - List assertions; try { typeResolver.defineSemTypes(typeAndClassDefs, typeCheckContext); - assertions = SemTypeAssertionTransformer.getTypeAssertionsFrom(fileName, pair.syntaxTree, - pkgNode.semtypeEnv); + return SemTypeAssertionTransformer.getTypeAssertionsFrom(fileName, pair.syntaxTree, typeTestEnv, + typeCheckContext, api); } catch (Exception e) { - assertions = new ArrayList<>(List.of(new SemTypeAssertionTransformer.TypeAssertion( + return List.of(new TypeAssertion<>( null, fileName, null, null, null, e.getMessage() - ))); + )); } - return assertions; } public void listAllBalFiles(File file, List balFiles) { @@ -152,10 +158,10 @@ public void listAllBalFiles(File file, List balFiles) { public final HashSet typeRelDirSkipList() { HashSet hashSet = new HashSet<>(); // intersection with negative (!) atom - hashSet.add("proj7-t.bal"); - hashSet.add("proj8-t.bal"); - hashSet.add("proj9-t.bal"); - hashSet.add("proj10-t.bal"); + hashSet.add("proj7-tv.bal"); + hashSet.add("proj8-tv.bal"); + hashSet.add("proj9-tv.bal"); + hashSet.add("proj10-tv.bal"); // Not a type test. This is an error test hashSet.add("xml-te.bal"); @@ -205,50 +211,186 @@ public void funcTest(String fileName) { @Test(expectedExceptions = AssertionError.class) public void shouldFailForIncorrectTestStructure() { File wrongAssertionFile = resolvePath("test-src/type-rel-wrong.bal").toFile(); - List typeAssertions = getTypeAssertions(wrongAssertionFile); + TypeCheckData utils = compilerTypeResolverUtilsFromFile(wrongAssertionFile); + List> typeAssertions = getTypeAssertions(wrongAssertionFile, + utils.resolver(), utils.context(), utils.env(), CompilerTypeTestAPI.getInstance(), utils.pair() + ); testSemTypeAssertions(typeAssertions.get(0)); } + @DataProvider(name = "runtimeFileNameProviderFunc") + public Object[] runtimeFileNameProviderFunc() { + File dataDir = resolvePath("test-src/type-rel").toFile(); + List balFiles = new ArrayList<>(); + listAllBalFiles(dataDir, balFiles); + Collections.sort(balFiles); + Predicate tableFilter = createRuntimeFileNameFilter(Set.of( + "anydata-tv.bal", + "table2-t.bal", + "table3-t.bal", + "table-readonly-t.bal", + "table-t.bal" + )); + Predicate listFilter = createRuntimeFileNameFilter(Set.of( + "bdddiff1-tv.bal", + "fixed-length-array-t.bal", + "fixed-length-array2-t.bal", + "fixed-length-array-large-t.bal", + "fixed-length-array-tuple-t.bal", + "list1-tv.bal", + "proj1-tv.bal", + "proj2-tv.bal", + "proj3-tv.bal", + "proj4-tv.bal", + "proj5-tv.bal", + "proj6-tv.bal", + "proj7-tv.bal", + "proj8-tv.bal", + "proj9-tv.bal", + "proj10-tv.bal", + "test_test.bal", + "tuple1-tv.bal", + "tuple2-tv.bal", + "tuple3-tv.bal", + "tuple4-tv.bal" + )); + Predicate functionFilter = createRuntimeFileNameFilter(Set.of( + "function2-tv.bal", + "function-intersection-tv.bal", + "function-param-tv.bal", + "function-rec-tv.bal", + "function-rest-tv.bal", + "function-tv.bal", + "function-union-tv.bal", + "func-quals-tv.bal" + )); + Predicate mappingFilter = createRuntimeFileNameFilter(Set.of( + "mapping1-t.bal", + "mapping2-t.bal", + "mapping3-t.bal", + "mapping-record-tv.bal", + "mapping-t.bal", + "mappingIntersect-tv.bal", + "mutable-record-t.bal", + "optional-field-record1-t.bal", + "optional-field-record2-t.bal", + "optional-field-record3-t.bal", + "record-proj-tv.bal", + "record-t.bal", + "recursive-record-t.bal", + "test_test.bal" + )); + Predicate xmlFilter = createRuntimeFileNameFilter(Set.of( + "xml-complex-ro-tv.bal", + "xml-complex-rw-tv.bal", + "xml-never-tv.bal", + "xml-readonly-tv.bal", + "xml-sequence-tv.bal", + "xml-te.bal" + )); + Predicate objectFilter = createRuntimeFileNameFilter(Set.of( + "object-binaryops-tv.bal", + "object-qulifiers-tv.bal", + "object-rec-tv.bal", + "object-simple-tv.bal", + "object-distinct-tv.bal" + )); + return balFiles.stream() + .filter(listFilter) + .filter(tableFilter) + .filter(functionFilter) + .filter(mappingFilter) + .filter(xmlFilter) + .filter(objectFilter) + .map(File::getAbsolutePath).toArray(); + } + + private static Predicate createRuntimeFileNameFilter(Set skipList) { + return file -> file.getName().endsWith(".bal") && !skipList.contains(file.getName()); + } + + @Test(dataProvider = "runtimeFileNameProviderFunc") + public void testRuntimeSemTypes(String fileName) { + File file = resolvePath(fileName).toFile(); + var utils = runtimeTypeResolverUtilsFromFile(file); + RuntimeTypeTestAPI api = RuntimeTypeTestAPI.getInstance(); + getTypeAssertions(file, + utils.resolver(), utils.context(), utils.env(), api, utils.pair()) + .forEach(a -> testAssertion(a, api)); + } + + private static TypeCheckData compilerTypeResolverUtilsFromFile(File file) { + String fileName = file.getAbsolutePath(); + BCompileUtil.PackageSyntaxTreePair pair = BCompileUtil.compileSemType(fileName); + BLangPackage pkgNode = pair.bLangPackage; + TypeTestContext context = ComplierTypeTestContext.from(Context.from(pkgNode.semtypeEnv)); + TypeTestEnv env = CompilerTypeTestEnv.from(pkgNode.semtypeEnv); + SemTypeResolver resolver = new CompilerSemTypeResolver(); + return new TypeCheckData<>(pair, context, env, resolver); + } + + private static TypeCheckData runtimeTypeResolverUtilsFromFile( + File file) { + String fileName = file.getAbsolutePath(); + BCompileUtil.PackageSyntaxTreePair pair = BCompileUtil.compileSemType(fileName); + TypeTestEnv env = RuntimeTypeTestEnv.from(); + TypeTestContext context = RuntimeTypeTestContext.from(env); + SemTypeResolver resolver = new RuntimeSemTypeResolver(); + return new TypeCheckData<>(pair, context, env, resolver); + } + + private record TypeCheckData(BCompileUtil.PackageSyntaxTreePair pair, TypeTestContext context, + TypeTestEnv env, SemTypeResolver resolver) { + + } + @Test(expectedExceptions = AssertionError.class) public void shouldFailForTooLargeLists() { File wrongAssertionFile = resolvePath("test-src/fixed-length-array-too-large-te.bal").toFile(); - List typeAssertions = getTypeAssertions(wrongAssertionFile); + TypeCheckData utils = compilerTypeResolverUtilsFromFile(wrongAssertionFile); + List> typeAssertions = getTypeAssertions(wrongAssertionFile, + utils.resolver(), utils.context(), utils.env(), CompilerTypeTestAPI.getInstance(), utils.pair() + ); testSemTypeAssertions(typeAssertions.get(0)); } @Test(dataProvider = "type-rel-provider") - public void testSemTypeAssertions(SemTypeAssertionTransformer.TypeAssertion typeAssertion) { + public void testSemTypeAssertions(TypeAssertion typeAssertion) { if (typeAssertion.kind() == null) { Assert.fail( "Exception thrown in " + typeAssertion.fileName() + System.lineSeparator() + typeAssertion.text()); } + testAssertion(typeAssertion, CompilerTypeTestAPI.getInstance()); + } + private void testAssertion(TypeAssertion typeAssertion, + TypeTestAPI semTypes) { switch (typeAssertion.kind()) { case NON: Assert.assertFalse( - SemTypes.isSubtype(typeAssertion.context(), typeAssertion.lhs(), typeAssertion.rhs()), - formatFailingAssertionDescription(typeAssertion)); + semTypes.isSubtype(typeAssertion.context(), typeAssertion.lhs(), typeAssertion.rhs()), + formatFailingAssertionDescription(typeAssertion)); Assert.assertFalse( - SemTypes.isSubtype(typeAssertion.context(), typeAssertion.rhs(), typeAssertion.lhs()), - formatFailingAssertionDescription(typeAssertion)); + semTypes.isSubtype(typeAssertion.context(), typeAssertion.rhs(), typeAssertion.lhs()), + formatFailingAssertionDescription(typeAssertion)); break; case SUB: - Assert.assertTrue(SemTypes.isSubtype(typeAssertion.context(), typeAssertion.lhs(), typeAssertion.rhs()), - formatFailingAssertionDescription(typeAssertion)); + Assert.assertTrue(semTypes.isSubtype(typeAssertion.context(), typeAssertion.lhs(), typeAssertion.rhs()), + formatFailingAssertionDescription(typeAssertion)); Assert.assertFalse( - SemTypes.isSubtype(typeAssertion.context(), typeAssertion.rhs(), typeAssertion.lhs()), + semTypes.isSubtype(typeAssertion.context(), typeAssertion.rhs(), typeAssertion.lhs()), formatFailingAssertionDescription(typeAssertion)); break; case SAME: - Assert.assertTrue(SemTypes.isSubtype(typeAssertion.context(), typeAssertion.lhs(), typeAssertion.rhs()), - formatFailingAssertionDescription(typeAssertion)); - Assert.assertTrue(SemTypes.isSubtype(typeAssertion.context(), typeAssertion.rhs(), typeAssertion.lhs()), - formatFailingAssertionDescription(typeAssertion)); + Assert.assertTrue(semTypes.isSubtype(typeAssertion.context(), typeAssertion.lhs(), typeAssertion.rhs()), + formatFailingAssertionDescription(typeAssertion)); + Assert.assertTrue(semTypes.isSubtype(typeAssertion.context(), typeAssertion.rhs(), typeAssertion.lhs()), + formatFailingAssertionDescription(typeAssertion)); } } @NotNull - private String formatFailingAssertionDescription(SemTypeAssertionTransformer.TypeAssertion typeAssertion) { + private String formatFailingAssertionDescription(TypeAssertion typeAssertion) { return typeAssertion.text() + "\n in: " + typeAssertion.fileName(); } @@ -274,11 +416,12 @@ private List getSubtypeRels(String sourceFilePath) { typeAndClassDefs.addAll(pkgNode.constants); typeAndClassDefs.addAll(pkgNode.typeDefinitions); - SemTypeResolver typeResolver = new SemTypeResolver(); - Context typeCheckContext = Context.from(pkgNode.semtypeEnv); + SemTypeResolver typeResolver = new CompilerSemTypeResolver(); + TypeTestContext typeCheckContext = + ComplierTypeTestContext.from(Context.from(pkgNode.semtypeEnv)); typeResolver.defineSemTypes(typeAndClassDefs, typeCheckContext); Map typeMap = pkgNode.semtypeEnv.getTypeNameSemTypeMap(); - + TypeTestAPI api = CompilerTypeTestAPI.getInstance(); List subtypeRelations = new ArrayList<>(); List typeNameList = typeMap.keySet().stream() .filter(n -> !n.startsWith("$anon")) @@ -292,10 +435,10 @@ private List getSubtypeRels(String sourceFilePath) { SemType t1 = typeMap.get(name1); SemType t2 = typeMap.get(name2); - if (SemTypes.isSubtype(typeCheckContext, t1, t2)) { + if (api.isSubtype(typeCheckContext, t1, t2)) { subtypeRelations.add(TypeRel.rel(name1, name2)); } - if (SemTypes.isSubtype(typeCheckContext, t2, t1)) { + if (api.isSubtype(typeCheckContext, t2, t1)) { subtypeRelations.add(TypeRel.rel(name2, name1)); } } diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeAssertion.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeAssertion.java new file mode 100644 index 000000000000..e4452711b6c1 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeAssertion.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +import java.nio.file.Paths; + +/** + * Subtype test. + * + * @param Which semtype (runtime or compiler) is used for type assertion. + * @param context Type context under which {@code SemTypes} were defined. + * @param fileName Name of the file in which types were defined in. + * @param lhs Resolved {@code SemType} for the Left-hand side of the subtype test. + * @param rhs Resolved {@code SemType} for the Right-hand side of the subtype test. + * @param kind Relationship between the two types. + * @param text Text that will be shown in case of assertion failure. + * @since 3.0.0 + */ +public record TypeAssertion(TypeTestContext context, String fileName, SemType lhs, SemType rhs, + SemTypeAssertionTransformer.RelKind kind, String text) { + + public TypeAssertion { + if (kind != null) { + assert lhs != null; + assert rhs != null; + } + } + + @Override + public String toString() { + return Paths.get(fileName).getFileName().toString() + ": " + text; + } +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestAPI.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestAPI.java new file mode 100644 index 000000000000..60dc1190abfb --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestAPI.java @@ -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. + */ + +package io.ballerina.semtype.port.test; + +public interface TypeTestAPI { + + boolean isSubtype(TypeTestContext cx, SemType t1, SemType t2); + + boolean isSubtypeSimple(SemType t1, SemType t2); + + boolean isListType(SemType t); + + boolean isMapType(SemType t); + + SemType intConst(long l); + + SemType mappingMemberTypeInnerVal(TypeTestContext context, SemType type, SemType m); + + SemType listProj(TypeTestContext context, SemType t, SemType key); + + SemType listMemberType(TypeTestContext context, SemType t, SemType key); +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestContext.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestContext.java new file mode 100644 index 000000000000..86402244cc03 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestContext.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +public interface TypeTestContext { + + TypeTestEnv getEnv(); + + Object getInnerEnv(); + + Object getInnerContext(); +} diff --git a/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestEnv.java b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestEnv.java new file mode 100644 index 000000000000..727232e5acd8 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestEnv.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +package io.ballerina.semtype.port.test; + +import java.util.Map; + +public interface TypeTestEnv { + + Map getTypeNameSemTypeMap(); + + void addTypeDef(String value, SemType semtype); +} diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/basic-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/basic-tv.bal new file mode 100644 index 000000000000..fabd47e8f069 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/basic-tv.bal @@ -0,0 +1,17 @@ +// @type T1 < T2 +// @type T1 <> T3 +type T1 1|1.0|"foo"; +type T2 int|float|string; +type T3 int|string; + +// @type T4 = OneFoo +type T4 T3 & T1; +type OneFoo 1|"foo"; + +// @type T5 = One +// @type DoubleOne = One +// @type AnotherDoubleOne = One +type T5 1|1; +type One 1; +type DoubleOne One|One; +type AnotherDoubleOne One|1; diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/decimal-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/decimal-tv.bal new file mode 100644 index 000000000000..257448a987f1 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/decimal-tv.bal @@ -0,0 +1,13 @@ +// @type PosZero < Decimal +// @type NegZero < Decimal +// @type OtherZero < Decimal +// @type PosZero = NegZero +// @type PosZero = OtherZero +type PosZero 0.0d; +type NegZero -0.0d; +type OtherZero 0d; + +type Decimal decimal; + +// @type IntZero <> OtherZero +type IntZero 0; diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/float-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/float-tv.bal new file mode 100644 index 000000000000..ecece93d1dc4 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/float-tv.bal @@ -0,0 +1,20 @@ +// @type NegativeZero < Float +// @type Zero < Float +// @type NegativeZero = Zero + +type Zero 0.0; +type NegativeZero -0.0; + +type Float float; + +// @type D1 <> Float +// @type D1 <> Zero +type D1 0.0d; + +// @type F1 < Float +// @type F1 = Zero +// @type F1 = NegativeZero +// @type F2 = Zero +// @type F2 = NegativeZero +type F1 Zero|NegativeZero; +type F2 F1 & Zero; diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj10-t.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj10-tv.bal similarity index 100% rename from tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj10-t.bal rename to tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj10-tv.bal diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj3-t.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj3-tv.bal similarity index 100% rename from tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj3-t.bal rename to tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj3-tv.bal diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj4-t.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj4-tv.bal similarity index 100% rename from tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj4-t.bal rename to tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj4-tv.bal diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj5-t.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj5-tv.bal similarity index 100% rename from tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj5-t.bal rename to tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj5-tv.bal diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj6-t.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj6-tv.bal similarity index 100% rename from tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj6-t.bal rename to tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj6-tv.bal diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj7-t.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj7-tv.bal similarity index 100% rename from tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj7-t.bal rename to tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj7-tv.bal diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj8-t.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj8-tv.bal similarity index 100% rename from tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj8-t.bal rename to tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj8-tv.bal diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj9-t.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj9-tv.bal similarity index 100% rename from tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj9-t.bal rename to tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/proj9-tv.bal diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/string-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/string-tv.bal new file mode 100644 index 000000000000..db4f84e356ac --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/string-tv.bal @@ -0,0 +1,16 @@ +// @type U1 < String +// @type U1 < Char +type U1 "අ"; +// @type U2 < String +// @type U2 < Char +type U2 "🛧"; +// @type C1 < String +// @type C1 < Char +type C1 "a"; + +// @type S1 < String +// @type S1 <> Char +type S1 "abc"; + +type String string; +type Char string:Char;