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/FunctionType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/FunctionType.java index 31795cd7d126..0c9b443e30cf 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/FunctionType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/FunctionType.java @@ -17,6 +17,8 @@ */ package io.ballerina.runtime.api.types; +import io.ballerina.runtime.internal.types.semtype.FunctionQualifiers; + /** * {@code FunctionType} represents a function type in ballerina. * @@ -39,4 +41,6 @@ public interface FunctionType extends AnnotatableType { Type getRestType(); Parameter[] getParameters(); + + FunctionQualifiers getQualifiers(); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/MethodType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/MethodType.java index 1f416679836e..7850e83d3303 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/MethodType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/MethodType.java @@ -34,4 +34,6 @@ public interface MethodType extends FunctionType { * @return true if {@link MethodType} method is isolated otherwise false. */ boolean isIsolated(); + + String name(); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Atom.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Atom.java new file mode 100644 index 000000000000..6d993b5f5bc8 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Atom.java @@ -0,0 +1,29 @@ +/* + * 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 the BDD atom. + * + * @since 2201.10.0 + */ +public interface Atom { + + int index(); +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/AtomicType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/AtomicType.java new file mode 100644 index 000000000000..f2286e2fd61d --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/AtomicType.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; + +/** + * Marker type representing AtomicType. + * + * @since 2201.10.0 + */ +public sealed interface AtomicType permits CellAtomicType, FunctionAtomicType, ListAtomicType, MappingAtomicType { + +} 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..8026f3ff15c1 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BasicTypeCode.java @@ -0,0 +1,134 @@ +/* + * 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; + + // TODO: see if we can turn this class to an enum with a value + // 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 BASIC_TYPE_MASK = (1 << (CODE_STRING + 1)) - 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; + } + + @Override + public int hashCode() { + return code; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof BasicTypeCode other) { + return code == other.code; + } + return false; + } + + 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/Bdd.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Bdd.java new file mode 100644 index 000000000000..a438b359afc2 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Bdd.java @@ -0,0 +1,254 @@ +/* + * 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; + +import static io.ballerina.runtime.api.types.semtype.Conjunction.and; + +/** + * Represent BDD node. Subtypes that uses BDDs to represent subtypes such as list, mapping and cell should implement + * their own {@code SubType} implementation that wraps an implementation of this class. + * + * @since 2201.10.0 + */ +public abstract sealed class Bdd extends SubType implements SubTypeData permits BddAllOrNothing, BddNode { + + Bdd(boolean all, boolean nothing) { + super(all, nothing); + } + + @Override + public SubType union(SubType other) { + return bddUnion((Bdd) other); + } + + private Bdd bddUnion(Bdd other) { + if (other == this) { + return this; + } else if (this == BddAllOrNothing.ALL || other == BddAllOrNothing.ALL) { + return BddAllOrNothing.ALL; + } else if (other == BddAllOrNothing.NOTHING) { + return this; + } else if (this == BddAllOrNothing.NOTHING) { + return other; + } + BddNode b1Bdd = (BddNode) this; + BddNode b2Bdd = (BddNode) other; + int cmp = atomCmp(b1Bdd.atom(), b2Bdd.atom()); + if (cmp < 0) { + return bddCreate(b1Bdd.atom(), + b1Bdd.left(), + b1Bdd.middle().bddUnion(other), + b1Bdd.right()); + } else if (cmp > 0) { + return bddCreate(b2Bdd.atom(), + b2Bdd.left(), + this.bddUnion(b2Bdd.middle()), + b2Bdd.right()); + } else { + return bddCreate(b1Bdd.atom(), + b1Bdd.left().bddUnion(b2Bdd.left()), + b1Bdd.middle().bddUnion(b2Bdd.middle()), + b1Bdd.right().bddUnion(b2Bdd.right())); + } + } + + private int atomCmp(Atom a1, Atom a2) { + if (a1 instanceof RecAtom r1) { + if (a2 instanceof RecAtom r2) { + return r1.index() - r2.index(); + } else { + return -1; + } + } else if (a2 instanceof RecAtom) { + return 1; + } else { + return a1.index() - a2.index(); + } + } + + @Override + public SubType intersect(SubType other) { + return bddIntersect((Bdd) other); + } + + private Bdd bddIntersect(Bdd other) { + if (other == this) { + return this; + } else if (this == BddAllOrNothing.NOTHING || other == BddAllOrNothing.NOTHING) { + return BddAllOrNothing.NOTHING; + } else if (other == BddAllOrNothing.ALL) { + return this; + } else if (this == BddAllOrNothing.ALL) { + return other; + } + BddNode b1Bdd = (BddNode) this; + BddNode b2Bdd = (BddNode) other; + int cmp = atomCmp(b1Bdd.atom(), b2Bdd.atom()); + if (cmp < 0) { + return bddCreate(b1Bdd.atom(), + b1Bdd.left().bddIntersect(other), + b1Bdd.middle().bddIntersect(other), + b1Bdd.right().bddIntersect(other)); + } else if (cmp > 0) { + return bddCreate(b2Bdd.atom(), + this.bddIntersect(b2Bdd.left()), + this.bddIntersect(b2Bdd.middle()), + this.bddIntersect(b2Bdd.right())); + } else { + return bddCreate(b1Bdd.atom(), + b1Bdd.left().bddUnion(b1Bdd.middle()).bddIntersect(b2Bdd.left().bddUnion(b2Bdd.middle())), + BddAllOrNothing.NOTHING, + b1Bdd.right().bddUnion(b1Bdd.middle()).bddIntersect(b2Bdd.right().bddUnion(b2Bdd.middle()))); + } + } + + @Override + public SubType diff(SubType other) { + return bddDiff((Bdd) other); + } + + private Bdd bddDiff(Bdd other) { + if (this == other || other == BddAllOrNothing.ALL || this == BddAllOrNothing.NOTHING) { + return BddAllOrNothing.NOTHING; + } else if (other == BddAllOrNothing.NOTHING) { + return this; + } else if (this == BddAllOrNothing.ALL) { + return other.bddComplement(); + } + BddNode b1Bdd = (BddNode) this; + BddNode b2Bdd = (BddNode) other; + int cmp = atomCmp(b1Bdd.atom(), b2Bdd.atom()); + if (cmp < 0L) { + return bddCreate(b1Bdd.atom(), + b1Bdd.left().bddUnion(b1Bdd.middle()).bddDiff(other), + BddAllOrNothing.NOTHING, + b1Bdd.right().bddUnion(b1Bdd.middle()).bddDiff(other)); + } else if (cmp > 0L) { + return bddCreate(b2Bdd.atom(), + this.bddDiff(b2Bdd.left().bddUnion(b2Bdd.middle())), + BddAllOrNothing.NOTHING, + this.bddDiff(b2Bdd.right().bddUnion(b2Bdd.middle()))); + } else { + // There is an error in the Castagna paper for this formula. + // The union needs to be materialized here. + // The original formula does not work in a case like (a0|a1) - a0. + // Castagna confirms that the following formula is the correct one. + return bddCreate(b1Bdd.atom(), + b1Bdd.left().bddUnion(b1Bdd.middle()).bddDiff(b2Bdd.left().bddUnion(b2Bdd.middle())), + BddAllOrNothing.NOTHING, + b1Bdd.right().bddUnion(b1Bdd.middle()).bddDiff(b2Bdd.right().bddUnion(b2Bdd.middle()))); + } + } + + @Override + public SubType complement() { + return bddComplement(); + } + + private Bdd bddComplement() { + if (this == BddAllOrNothing.ALL) { + return BddAllOrNothing.NOTHING; + } else if (this == BddAllOrNothing.NOTHING) { + return BddAllOrNothing.ALL; + } + Bdd nothing = BddAllOrNothing.NOTHING; + BddNode b = (BddNode) this; + if (b.right() == nothing) { + return bddCreate(b.atom(), + nothing, + b.left().bddUnion(b.middle()).bddComplement(), + b.middle().bddComplement()); + } else if (b.left() == nothing) { + return bddCreate(b.atom(), + b.middle().bddComplement(), + b.right().bddUnion(b.middle()).bddComplement(), + nothing); + } else if (b.middle() == nothing) { + return bddCreate(b.atom(), + b.left().bddComplement(), + b.left().bddUnion(b.right()).bddComplement(), + b.right().bddComplement()); + } else { + // There is a typo in the Frisch PhD thesis for this formula. + // (It has left and right swapped.) + // Castagna (the PhD supervisor) confirms that this is the correct formula. + return bddCreate(b.atom(), + b.left().bddUnion(b.middle()).bddComplement(), + nothing, + b.right().bddUnion(b.middle()).bddComplement()); + } + } + + private Bdd bddCreate(Atom atom, Bdd left, Bdd middle, Bdd right) { + if (middle == BddAllOrNothing.ALL) { + return middle; + } + if (left.equals(right)) { + return left.bddUnion(right); + } + + return new BddNode(atom, left, middle, right); + } + + @Override + public boolean isEmpty(Context cx) { + // Basic types that uses Bdd as a delegate should implement isEmpty instead. + throw new IllegalStateException("Bdd don't support isEmpty"); + } + + @Override + public SubTypeData data() { + // Basic types that uses Bdd (and has a meaningful data part) as a delegate should implement data instead. + throw new IllegalStateException("Bdd don't support data"); + } + + public static boolean bddEvery(Context cx, Bdd b, Conjunction pos, Conjunction neg, BddPredicate predicate) { + if (b instanceof BddAllOrNothing allOrNothing) { + return allOrNothing == BddAllOrNothing.NOTHING || predicate.apply(cx, pos, neg); + } + BddNode bn = (BddNode) b; + return bddEvery(cx, bn.left(), and(bn.atom(), pos), neg, predicate) + && bddEvery(cx, bn.middle(), pos, neg, predicate) + && bddEvery(cx, bn.right(), pos, and(bn.atom(), neg), predicate); + } + + public static boolean bddEveryPositive(Context cx, Bdd b, Conjunction pos, Conjunction neg, + BddPredicate predicate) { + if (b instanceof BddAllOrNothing allOrNothing) { + return allOrNothing == BddAllOrNothing.NOTHING || predicate.apply(cx, pos, neg); + } else { + BddNode bn = (BddNode) b; + return bddEveryPositive(cx, bn.left(), andIfPositive(bn.atom(), pos), neg, predicate) + && bddEveryPositive(cx, bn.middle(), pos, neg, predicate) + && bddEveryPositive(cx, bn.right(), pos, andIfPositive(bn.atom(), neg), predicate); + } + } + + private static Conjunction andIfPositive(Atom atom, Conjunction next) { + if (atom instanceof RecAtom recAtom && recAtom.index() < 0) { + return next; + } + return and(atom, next); + } + + public abstract boolean posMaybeEmpty(); + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddAllOrNothing.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddAllOrNothing.java new file mode 100644 index 000000000000..f12f3c12f48b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddAllOrNothing.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.runtime.api.types.semtype; + +/** + * Represent the leaf node of a Bdd. + * + * @since 2201.10.0 + */ +public final class BddAllOrNothing extends Bdd { + + public static final BddAllOrNothing ALL = new BddAllOrNothing(true); + public static final BddAllOrNothing NOTHING = new BddAllOrNothing(false); + + private BddAllOrNothing(boolean all) { + super(all, !all); + } + + @Override + public int hashCode() { + return this == ALL ? 1 : 0; + } + + @Override + public boolean equals(Object o) { + return this == o; + } + + @Override + public boolean posMaybeEmpty() { + return this == ALL; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddIsEmptyPredicate.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddIsEmptyPredicate.java new file mode 100644 index 000000000000..4b1f7bb6afa9 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddIsEmptyPredicate.java @@ -0,0 +1,25 @@ +/* + * 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; + +@FunctionalInterface +public interface BddIsEmptyPredicate { + + boolean apply(Context cx, Bdd bdd); +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddMemo.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddMemo.java new file mode 100644 index 000000000000..30beff46fdb2 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddMemo.java @@ -0,0 +1,56 @@ +/* + * 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 java.util.Objects; + +// TODO: consider moving this to inner as well +public final class BddMemo { + + public Status isEmpty; + + public BddMemo() { + this.isEmpty = Status.NULL; + } + + public enum Status { + TRUE, + FALSE, + LOOP, + CYCLIC, + PROVISIONAL, + NULL + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BddMemo bddMemo)) { + return false; + } + return isEmpty == bddMemo.isEmpty; + } + + @Override + public int hashCode() { + return Objects.hashCode(isEmpty); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNode.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNode.java new file mode 100644 index 000000000000..ec0991eb66be --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNode.java @@ -0,0 +1,105 @@ +/* + * 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; + +/** + * Internal node of a BDD, which represents a disjunction of conjunctions of atoms. + * + * @since 2201.10.0 + */ +public final class BddNode extends Bdd { + + private final Atom atom; + private final Bdd left; + private final Bdd middle; + private final Bdd right; + private volatile Integer hashCode = null; + + BddNode(Atom atom, Bdd left, Bdd middle, Bdd right) { + super(false, false); + this.atom = atom; + this.left = left; + this.middle = middle; + this.right = right; + } + + public static BddNode bddAtom(Atom atom) { + return new BddNode(atom, BddAllOrNothing.ALL, BddAllOrNothing.NOTHING, BddAllOrNothing.NOTHING); + } + + public Atom atom() { + return atom; + } + + public Bdd left() { + return left; + } + + public Bdd middle() { + return middle; + } + + public Bdd right() { + return right; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof BddNode other)) { + return false; + } + return atom.equals(other.atom) && left.equals(other.left) && middle.equals(other.middle) && + right.equals(other.right); + } + + @Override + public int hashCode() { + Integer result = hashCode; + if (result == null) { + synchronized (this) { + result = hashCode; + if (result == null) { + hashCode = result = computeHashCode(); + } + } + } + return result; + } + + private int computeHashCode() { + int result = atom.hashCode(); + result = 31 * result + left.hashCode(); + result = 31 * result + middle.hashCode(); + result = 31 * result + right.hashCode(); + return result; + } + + boolean isSimple() { + return left.equals(BddAllOrNothing.ALL) && middle.equals(BddAllOrNothing.NOTHING) && + right.equals(BddAllOrNothing.NOTHING); + } + + @Override + public boolean posMaybeEmpty() { + return middle.posMaybeEmpty() || right.posMaybeEmpty(); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddPredicate.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddPredicate.java new file mode 100644 index 000000000000..ec2cbea729f0 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddPredicate.java @@ -0,0 +1,31 @@ +/* + * 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.api.types.semtype; + +/** + * Represents a predicate that can be applied to a BDD conjunction. + * + * @since 2201.10.0 + */ +@FunctionalInterface +public interface BddPredicate { + + boolean apply(Context cx, Conjunction posList, Conjunction negList); +} 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..668a7da007fc --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Builder.java @@ -0,0 +1,470 @@ +/* + * 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.BArray; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.internal.types.BType; +import io.ballerina.runtime.internal.types.TypeWithShape; +import io.ballerina.runtime.internal.types.semtype.BBooleanSubType; +import io.ballerina.runtime.internal.types.semtype.BCellSubType; +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.BListSubType; +import io.ballerina.runtime.internal.types.semtype.BMappingSubType; +import io.ballerina.runtime.internal.types.semtype.BObjectSubType; +import io.ballerina.runtime.internal.types.semtype.BStringSubType; +import io.ballerina.runtime.internal.types.semtype.FixedLengthArray; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; +import io.ballerina.runtime.internal.types.semtype.MappingDefinition; +import io.ballerina.runtime.internal.values.AbstractObjectValue; +import io.ballerina.runtime.internal.values.DecimalValue; +import io.ballerina.runtime.internal.values.FPValue; + +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.BT_CELL; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_ERROR; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_FUNCTION; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_LIST; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_MAPPING; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_OBJECT; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_B_TYPE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.VT_INHERENTLY_IMMUTABLE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.VT_MASK; +import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_LIMITED; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; +import static io.ballerina.runtime.api.types.semtype.Core.union; + +/** + * Utility class for creating semtypes. + * + * @since 2201.10.0 + */ +public final class Builder { + + private static final String[] EMPTY_STRING_ARR = new String[0]; + private static final SemType VAL = SemType.from(VT_MASK); + private static final SemType OBJECT = from(BT_OBJECT); + + private static final SemType INNER = basicTypeUnion(VAL.all | from(BasicTypeCode.BT_UNDEF).all); + private static final SemType ANY = basicTypeUnion(BasicTypeCode.VT_MASK & ~(1 << BasicTypeCode.BT_ERROR.code())); + private static final SemType SIMPLE_OR_STRING = + basicTypeUnion((1 << BasicTypeCode.BT_NIL.code()) + | (1 << BasicTypeCode.BT_BOOLEAN.code()) + | (1 << BasicTypeCode.BT_INT.code()) + | (1 << BasicTypeCode.BT_FLOAT.code()) + | (1 << BasicTypeCode.BT_DECIMAL.code()) + | (1 << BasicTypeCode.BT_STRING.code())); + + private static final SemType[] EMPTY_TYPES_ARR = new SemType[0]; + + private static final int BDD_REC_ATOM_OBJECT_READONLY = 1; + private static final RecAtom OBJECT_RO_REC_ATOM = RecAtom.createRecAtom(BDD_REC_ATOM_OBJECT_READONLY); + + public static final BddNode MAPPING_SUBTYPE_OBJECT_RO = bddAtom(OBJECT_RO_REC_ATOM); + private static final ConcurrentLazyContainer READONLY_TYPE = new ConcurrentLazyContainer<>(() -> unionOf( + SemType.from(VT_INHERENTLY_IMMUTABLE), + basicSubType(BT_LIST, BListSubType.createDelegate(bddSubtypeRo())), + basicSubType(BT_MAPPING, BMappingSubType.createDelegate(bddSubtypeRo())), + basicSubType(BT_OBJECT, BObjectSubType.createDelegate(MAPPING_SUBTYPE_OBJECT_RO)) + )); + private static final ConcurrentLazyContainer MAPPING_RO = new ConcurrentLazyContainer<>(() -> + basicSubType(BT_MAPPING, BMappingSubType.createDelegate(bddSubtypeRo())) + ); + private static final ConcurrentLazyContainer INNER_RO = + new ConcurrentLazyContainer<>(() -> union(readonlyType(), inner())); + + private static final ConcurrentLazyContainer LIST_ATOMIC_INNER = + new ConcurrentLazyContainer<>(() -> new ListAtomicType( + FixedLengthArray.empty(), PredefinedTypeEnv.getInstance().cellSemTypeInner())); + private static final ConcurrentLazyContainer MAPPING_ATOMIC_INNER = + new ConcurrentLazyContainer<>(() -> new MappingAtomicType( + EMPTY_STRING_ARR, EMPTY_TYPES_ARR, PredefinedTypeEnv.getInstance().cellSemTypeInner())); + + private static final PredefinedTypeEnv PREDEFINED_TYPE_ENV = PredefinedTypeEnv.getInstance(); + + 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(Context cx, Type type) { + if (type instanceof SemType semType) { + return semType; + } else if (type instanceof BType bType) { + return fromBType(cx, bType); + } + throw new IllegalArgumentException("Unsupported type: " + type); + } + + private static SemType fromBType(Context cx, BType innerType) { + int staringSize = cx.addProvisionalType(innerType); + SemType result = innerType.get(cx); + cx.emptyProvisionalTypes(staringSize); + return result; + } + + public static SemType neverType() { + return SemType.from(0); + } + + public static SemType nilType() { + return from(BasicTypeCode.BT_NIL); + } + + public static SemType undef() { + return from(BasicTypeCode.BT_UNDEF); + } + + public static SemType cell() { + return from(BT_CELL); + } + + public static SemType inner() { + return INNER; + } + + 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; + } + + public static SemType listType() { + return from(BT_LIST); + } + + public static SemType readonlyType() { + return READONLY_TYPE.get(); + } + + static SemType basicTypeUnion(int bitset) { + return switch (bitset) { + case 0 -> neverType(); + case VT_MASK -> VAL; + default -> { + if (Integer.bitCount(bitset) == 1) { + int code = Integer.numberOfTrailingZeros(bitset); + if (BasicTypeCache.isCached(code)) { + yield BasicTypeCache.cache[code]; + } + } + yield SemType.from(bitset); + } + }; + } + + public static SemType basicSubType(BasicTypeCode basicTypeCode, SubType subType) { + assert !(subType instanceof Bdd) : "BDD should always be wrapped with a delegate"; + assert checkDelegate(basicTypeCode, subType); + int some = 1 << basicTypeCode.code(); + SubType[] subTypes = initializeSubtypeArray(some); + subTypes[0] = subType; + return SemType.from(0, some, subTypes); + } + + private static boolean checkDelegate(BasicTypeCode basicTypeCode, SubType subType) { + return (basicTypeCode != BT_MAPPING || subType instanceof BMappingSubType) + && (basicTypeCode != BT_LIST || subType instanceof BListSubType) + && (basicTypeCode != BT_CELL || subType instanceof BCellSubType); + } + + 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(int some) { + return new SubType[Integer.bitCount(some)]; + } + + public static Optional shapeOf(Context cx, 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())); + } else if (object instanceof BArray arrayValue) { + return typeOfArray(cx, arrayValue); + } else if (object instanceof BMap mapValue) { + return typeOfMap(cx, mapValue); + } else if (object instanceof FPValue fpValue) { + // TODO: this is a hack to support partial function types, remove when semtypes are fully implemented + return Optional.of(from(cx, fpValue.getType())); + } else if (object instanceof BError errorValue) { + return typeOfError(cx, errorValue); + } else if (object instanceof AbstractObjectValue objectValue) { + return typeOfObject(cx, objectValue); + } + return Optional.empty(); + } + + private static Optional typeOfError(Context cx, BError errorValue) { + TypeWithShape typeWithShape = (TypeWithShape) errorValue.getType(); + return typeWithShape.shapeOf(cx, errorValue); + } + + private static Optional typeOfMap(Context cx, BMap mapValue) { + TypeWithShape typeWithShape = (TypeWithShape) mapValue.getType(); + return typeWithShape.shapeOf(cx, mapValue); + } + + private static Optional typeOfObject(Context cx, AbstractObjectValue objectValue) { + TypeWithShape typeWithShape = (TypeWithShape) objectValue.getType(); + return typeWithShape.shapeOf(cx, objectValue); + } + + private static Optional typeOfArray(Context cx, BArray arrayValue) { + TypeWithShape typeWithShape = (TypeWithShape) arrayValue.getType(); + return typeWithShape.shapeOf(cx, arrayValue); + } + + public static SemType roCellContaining(Env env, SemType ty) { + return cellContaining(env, ty, CELL_MUT_NONE); + } + + public static SemType cellContaining(Env env, SemType ty) { + return cellContaining(env, ty, CellAtomicType.CellMutability.CELL_MUT_LIMITED); + } + + public static SemType cellContaining(Env env, SemType ty, CellAtomicType.CellMutability mut) { + Optional cachedSemType = env.getCachedCellType(ty, mut); + return cachedSemType.orElseGet(() -> { + SemType semType = createCellSemType(env, ty, mut); + env.cacheCellType(ty, mut, semType); + return semType; + }); + } + + private static SemType createCellSemType(Env env, SemType ty, CellAtomicType.CellMutability mut) { + CellAtomicType atomicCell = new CellAtomicType(ty, mut); + TypeAtom atom = env.cellAtom(atomicCell); + BddNode bdd = bddAtom(atom); + return basicSubType(BT_CELL, BCellSubType.createDelegate(bdd)); + } + + public static SemType valType() { + return basicTypeUnion(VT_MASK); + } + + public static SemType anyType() { + return ANY; + } + + public static SemType mappingType() { + return from(BT_MAPPING); + } + + public static SemType functionType() { + return from(BT_FUNCTION); + } + + public static SemType errorType() { + return from(BT_ERROR); + } + + + public static SemType anyDataType(Context context) { + SemType memo = context.anydataMemo; + if (memo != null) { + return memo; + } + Env env = context.env; + ListDefinition listDef = new ListDefinition(); + MappingDefinition mapDef = new MappingDefinition(); + // TODO: add table, xml + SemType accum = unionOf(SIMPLE_OR_STRING, listDef.getSemType(env), mapDef.getSemType(env)); + listDef.defineListTypeWrapped(env, EMPTY_TYPES_ARR, 0, accum, CELL_MUT_LIMITED); + mapDef.defineMappingTypeWrapped(env, new MappingDefinition.Field[0], accum, CELL_MUT_LIMITED); + context.anydataMemo = accum; + return accum; + } + + private static SemType unionOf(SemType... types) { + SemType accum = types[0]; + for (int i = 1; i < types.length; i++) { + accum = union(accum, types[i]); + } + return accum; + } + + public static SemType objectType() { + return OBJECT; + } + + static SemType mappingRO() { + return MAPPING_RO.get(); + } + + static SemType innerReadOnly() { + return INNER_RO.get(); + } + + static CellAtomicType cellAtomicVal() { + return PREDEFINED_TYPE_ENV.cellAtomicVal(); + } + + public static BddNode bddSubtypeRo() { + return bddAtom(RecAtom.createRecAtom(0)); + } + + public static ListAtomicType listAtomicInner() { + return LIST_ATOMIC_INNER.get(); + } + + public static MappingAtomicType mappingAtomicInner() { + return MAPPING_ATOMIC_INNER.get(); + } + + 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/CellAtomicType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/CellAtomicType.java new file mode 100644 index 000000000000..5ef2f0282a40 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/CellAtomicType.java @@ -0,0 +1,54 @@ +/* + * 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; + +/** + * CellAtomicType node. + * + * @param ty Type "wrapped" by this cell + * @param mut Mutability of the cell + * @since 2201.10.0 + */ +public record CellAtomicType(SemType ty, CellMutability mut) implements AtomicType { + + public CellAtomicType { + assert ty != null; + } + + public static CellAtomicType from(SemType ty, CellMutability mut) { + return new CellAtomicType(ty, mut); + } + + public static CellAtomicType intersectCellAtomicType(CellAtomicType c1, CellAtomicType c2) { + SemType ty = Core.intersect(c1.ty(), c2.ty()); + CellMutability mut = min(c1.mut(), c2.mut()); + return new CellAtomicType(ty, mut); + } + + private static CellMutability min(CellMutability m1, + CellMutability m2) { + return m1.compareTo(m2) <= 0 ? m1 : m2; + } + + public enum CellMutability { + CELL_MUT_NONE, + CELL_MUT_LIMITED, + CELL_MUT_UNLIMITED + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Conjunction.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Conjunction.java new file mode 100644 index 000000000000..34fbf71ded29 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Conjunction.java @@ -0,0 +1,34 @@ +/* + * 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.api.types.semtype; + +/** + * Represents the Conjunction in the BDD. + * + * @param atom Atom of this node + * @param next Next node in the conjunction, will be {@code null} if this is the last node + * @since 2201.10.0 + */ +public record Conjunction(Atom atom, Conjunction next) { + + public static Conjunction and(Atom atom, Conjunction next) { + return new Conjunction(atom, next); + } +} 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..0842e8809e65 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Context.java @@ -0,0 +1,157 @@ +/* + * 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.BType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Context in which type checking operations are performed. Note context is not thread safe, requiring external + * synchronization if shared between threads. Multiple contexts may share same environment without issue. + * + * @since 2201.10.0 + */ +public final class Context { + + // Contains all BddMemo entries with isEmpty == PROVISIONAL + private final List memoStack = new ArrayList<>(); + public final Env env; + public final Map listMemo = new HashMap<>(); + public final Map mappingMemo = new HashMap<>(); + public final Map functionMemo = new HashMap<>(); + + private final List provisionalTypes = new ArrayList<>(); + private boolean resetProvisionalTypes = false; + + SemType anydataMemo; + private Context(Env env) { + this.env = env; + } + + public static Context from(Env env) { + return new Context(env); + } + + public boolean memoSubtypeIsEmpty(Map memoTable, BddIsEmptyPredicate isEmptyPredicate, Bdd bdd) { + BddMemo mm = memoTable.get(bdd); + BddMemo m; + if (mm != null) { + switch (mm.isEmpty) { + case CYCLIC: + // Since we define types inductively we consider these to be empty + return true; + case TRUE, FALSE: + // We know whether b is empty or not for certain + return mm.isEmpty == BddMemo.Status.TRUE; + case NULL: + // this is same as not having memo so fall through + m = mm; + break; + case LOOP, PROVISIONAL: + // We've got a loop. + mm.isEmpty = BddMemo.Status.LOOP; + return true; + default: + throw new AssertionError("Unexpected memo status: " + mm.isEmpty); + } + } else { + m = new BddMemo(); + memoTable.put(bdd, m); + } + m.isEmpty = BddMemo.Status.PROVISIONAL; + int initStackDepth = memoStack.size(); + memoStack.add(m); + boolean isEmpty = isEmptyPredicate.apply(this, bdd); + boolean isLoop = m.isEmpty == BddMemo.Status.LOOP; + if (!isEmpty || initStackDepth == 0) { + for (int i = initStackDepth + 1; i < memoStack.size(); i++) { + BddMemo.Status memoStatus = memoStack.get(i).isEmpty; + if (Objects.requireNonNull(memoStatus) == BddMemo.Status.PROVISIONAL || + memoStatus == BddMemo.Status.LOOP || memoStatus == BddMemo.Status.CYCLIC) { + memoStack.get(i).isEmpty = isEmpty ? BddMemo.Status.TRUE : BddMemo.Status.NULL; + } + } + if (memoStack.size() > initStackDepth) { + memoStack.subList(initStackDepth, memoStack.size()).clear(); + } + // The only way that we have found that this can be empty is by going through a loop. + // This means that the shapes in the type would all be infinite. + // But we define types inductively, which means we only consider finite shapes. + if (isLoop && isEmpty) { + m.isEmpty = BddMemo.Status.CYCLIC; + } else { + m.isEmpty = isEmpty ? BddMemo.Status.TRUE : BddMemo.Status.FALSE; + } + } + return isEmpty; + } + + public ListAtomicType listAtomType(Atom atom) { + if (atom instanceof RecAtom recAtom) { + return this.env.getRecListAtomType(recAtom); + } else { + return (ListAtomicType) ((TypeAtom) atom).atomicType(); + } + } + + public MappingAtomicType mappingAtomType(Atom atom) { + if (atom instanceof RecAtom recAtom) { + return this.env.getRecMappingAtomType(recAtom); + } else { + return (MappingAtomicType) ((TypeAtom) atom).atomicType(); + } + } + + public FunctionAtomicType functionAtomicType(Atom atom) { + if (atom instanceof RecAtom recAtom) { + return this.env.getRecFunctionAtomType(recAtom); + } else { + return (FunctionAtomicType) ((TypeAtom) atom).atomicType(); + } + } + + public int addProvisionalType(BType type) { + int currentSize = provisionalTypes.size(); + provisionalTypes.add(type); + return currentSize; + } + + public void markProvisionTypeReset() { + resetProvisionalTypes = true; + } + + public void emptyProvisionalTypes(int startingSize) { + if (startingSize != 0) { + return; + } + if (resetProvisionalTypes) { + for (int i = 1; i < provisionalTypes.size(); i++) { + BType type = provisionalTypes.get(i); + type.resetSemTypeCache(); + } + } + provisionalTypes.clear(); + resetProvisionalTypes = false; + } +} 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..a015217b8d94 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Core.java @@ -0,0 +1,413 @@ +/* + * 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.BObjectSubType; +import io.ballerina.runtime.internal.types.semtype.DelegatedSubType; +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 java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_B_TYPE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_CELL; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_INT; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_LIST; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_STRING; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_OBJECT; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_UNDEF; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.VT_MASK; +import static io.ballerina.runtime.api.types.semtype.Builder.cellContaining; +import static io.ballerina.runtime.api.types.semtype.Builder.listType; +import static io.ballerina.runtime.api.types.semtype.Builder.undef; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.intersectCellAtomicType; +import static io.ballerina.runtime.internal.types.semtype.BCellSubType.cellAtomType; +import static io.ballerina.runtime.internal.types.semtype.BListSubType.bddListMemberTypeInnerVal; + +/** + * 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(some); + int i = 0; + boolean filterNulls = false; + 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); + filterNulls = true; + } else if (data.isNothing()) { + some &= ~(1 << code); + filterNulls = true; + } else { + subtypes[i] = data; + } + i++; + } + return SemType.from(all, some, filterNulls ? filterNulls(subtypes) : subtypes); + } + + public static SubType getComplexSubtypeData(SemType t, BasicTypeCode code) { + assert (t.some() & (1 << code.code())) != 0; + SubType subType = t.subTypeByCode(code.code()); + if (subType instanceof DelegatedSubType wrapper) { + return wrapper.inner(); + } + return subType; + } + + // This computes the spec operation called "member type of K in T", + // for the case when T is a subtype of list, and K is either `int` or a singleton int. + // This is what Castagna calls projection. + // We will extend this to allow `key` to be a SemType, which will turn into an IntSubtype. + // If `t` is not a list, NEVER is returned + public static SemType listMemberTypeInnerVal(Context cx, SemType t, SemType k) { + if (t.some == 0) { + return (t.all & listType().all) != 0 ? Builder.valType() : Builder.neverType(); + } else { + SubTypeData keyData = intSubtype(k); + if (isNothingSubtype(keyData)) { + return Builder.neverType(); + } + return bddListMemberTypeInnerVal(cx, (Bdd) getComplexSubtypeData(t, BT_LIST), keyData, Builder.valType()); + } + } + + public static SemType union(SemType t1, SemType t2) { + assert t1 != null && t2 != null; + 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(some); + int i = 0; + boolean filterNulls = false; + 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()) { + filterNulls = true; + all |= 1 << code; + some &= ~(1 << code); + } else { + subtypes[i] = data; + } + i++; + } + if (some == 0) { + return SemType.from(all); + } + return SemType.from(all, some, filterNulls ? filterNulls(subtypes) : subtypes); + } + + private static SubType[] filterNulls(SubType[] subtypes) { + return Arrays.stream(subtypes).filter(Objects::nonNull).toArray(SubType[]::new); + } + + public static SemType intersect(SemType t1, SemType t2) { + assert t1 != null && t2 != null; + 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(some); + int i = 0; + boolean filterNulls = false; + 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[i] = data; + } else { + some &= ~(1 << code); + filterNulls = true; + } + i++; + } + if (some == 0) { + return SemType.from(all); + } + return SemType.from(all, some, filterNulls ? filterNulls(subtypes) : 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()) { + assert subType != null : "subtype array must not be sparse"; + if (!subType.isEmpty(cx)) { + return false; + } + } + return true; + } + + public static SemType complement(SemType t) { + return diff(Builder.valType(), t); + } + + 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 +// SemType.CachedResult cached = t1.cachedSubTypeRelation(t2); +// if (cached != SemType.CachedResult.NOT_FOUND) { +// return cached == SemType.CachedResult.TRUE; +// } + boolean result = isEmpty(cx, diff(t1, t2)); +// t1.cacheSubTypeRelation(t2, result); + return result; + } + + public static boolean isSubtypeSimple(SemType t1, SemType t2) { + assert t1 != null && t2 != null; + int bits = t1.all | t1.some; + return (bits & ~t2.all()) == 0; + } + + public static boolean isNothingSubtype(SubTypeData data) { + return data == AllOrNothing.NOTHING; + } + + // Describes the subtype of int included in the type: true/false mean all or none of string + public static SubTypeData intSubtype(SemType t) { + return subTypeData(t, BT_INT); + } + + // Describes the subtype of string included in the type: true/false mean all or none of string + public static SubTypeData stringSubtype(SemType t) { + return subTypeData(t, BT_STRING); + } + + 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; + } + SubType subType = s.subTypeByCode(code.code()); + assert subType != null; + return subType.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 SemType 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); + } + + public static SemType cellContainingInnerVal(Env env, SemType t) { + CellAtomicType cat = + cellAtomicType(t).orElseThrow(() -> new IllegalArgumentException("t is not a cell semtype")); + return cellContaining(env, diff(cat.ty(), undef()), cat.mut()); + } + + public static SemType intersectMemberSemTypes(Env env, SemType t1, SemType t2) { + CellAtomicType c1 = + cellAtomicType(t1).orElseThrow(() -> new IllegalArgumentException("t1 is not a cell semtype")); + CellAtomicType c2 = + cellAtomicType(t2).orElseThrow(() -> new IllegalArgumentException("t2 is not a cell semtype")); + + CellAtomicType atomicType = intersectCellAtomicType(c1, c2); + return cellContaining(env, atomicType.ty(), undef().equals(atomicType.ty()) ? CELL_MUT_NONE : atomicType.mut()); + } + + private static Optional cellAtomicType(SemType t) { + SemType cell = Builder.cell(); + if (t.some == 0) { + return cell.equals(t) ? Optional.of(Builder.cellAtomicVal()) : Optional.empty(); + } else { + if (!isSubtypeSimple(t, cell)) { + return Optional.empty(); + } + return bddCellAtomicType((Bdd) getComplexSubtypeData(t, BT_CELL), Builder.cellAtomicVal()); + } + } + + private static Optional bddCellAtomicType(Bdd bdd, CellAtomicType top) { + if (bdd instanceof BddAllOrNothing allOrNothing) { + if (allOrNothing.isAll()) { + return Optional.of(top); + } + return Optional.empty(); + } + BddNode bddNode = (BddNode) bdd; + return bddNode.isSimple() ? Optional.of(cellAtomType(bddNode.atom())) : Optional.empty(); + } + + public static SemType cellInnerVal(SemType t) { + return diff(cellInner(t), undef()); + } + + public static SemType cellInner(SemType t) { + CellAtomicType cat = + cellAtomicType(t).orElseThrow(() -> new IllegalArgumentException("t is not a cell semtype")); + return cat.ty(); + } + + public static SemType createBasicSemType(BasicTypeCode typeCode, Bdd bdd) { + if (bdd instanceof BddAllOrNothing) { + return bdd.isAll() ? Builder.from(typeCode) : Builder.neverType(); + } + SubType subType = switch (typeCode.code()) { + case CODE_OBJECT -> BObjectSubType.createDelegate(bdd); + default -> throw new IllegalArgumentException("Unexpected type code: " + typeCode); + }; + return SemType.from(0, 1 << typeCode.code(), new SubType[]{subType}); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Env.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Env.java new file mode 100644 index 000000000000..3ffd01b7d4ca --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Env.java @@ -0,0 +1,216 @@ +/* + * 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Represent the environment in which {@code SemTypes} are defined in. Type checking types defined in different + * environments with each other in undefined. This is safe to be shared between multiple threads. + * @since 2201.10.0 + */ +public final class Env { + // Currently there is no reason to worry about above restrictions since Env is a singleton, but strictly speaking + // there is not technical restriction to have multiple instances of Env. + + private static final Env INSTANCE = new Env(); + + private final Map atomTable; + private final ReadWriteLock atomTableLock = new ReentrantReadWriteLock(); + + private final ReadWriteLock recListLock = new ReentrantReadWriteLock(); + final List recListAtoms; + + private final ReadWriteLock recMapLock = new ReentrantReadWriteLock(); + final List recMappingAtoms; + + private final ReadWriteLock recFunctionLock = new ReentrantReadWriteLock(); + private final List recFunctionAtoms; + + private final Map cellTypeCache = new ConcurrentHashMap<>(); + + private final AtomicInteger distinctAtomCount = new AtomicInteger(0); + + private Env() { + this.atomTable = new HashMap<>(); + this.recListAtoms = new ArrayList<>(); + this.recMappingAtoms = new ArrayList<>(); + this.recFunctionAtoms = new ArrayList<>(); + + PredefinedTypeEnv.getInstance().initializeEnv(this); + } + + public static Env getInstance() { + return INSTANCE; + } + + public TypeAtom cellAtom(CellAtomicType atomicType) { + return this.typeAtom(atomicType); + } + + private TypeAtom typeAtom(AtomicType atomicType) { + atomTableLock.readLock().lock(); + try { + TypeAtom ta = this.atomTable.get(atomicType); + if (ta != null) { + return ta; + } + } finally { + atomTableLock.readLock().unlock(); + } + + atomTableLock.writeLock().lock(); + try { + // we are double-checking since there may be 2 trying to add at the same time + TypeAtom ta = this.atomTable.get(atomicType); + if (ta != null) { + return ta; + } else { + TypeAtom result = TypeAtom.createTypeAtom(this.atomTable.size() + 1, atomicType); + this.atomTable.put(result.atomicType(), result); + return result; + } + } finally { + atomTableLock.writeLock().unlock(); + } + } + + Optional getCachedCellType(SemType ty, CellAtomicType.CellMutability mut) { + return Optional.ofNullable(this.cellTypeCache.get(new CellSemTypeCacheKey(ty, mut))); + } + + void cacheCellType(SemType ty, CellAtomicType.CellMutability mut, SemType semType) { + this.cellTypeCache.put(new CellSemTypeCacheKey(ty, mut), semType); + } + + public RecAtom recListAtom() { + recListLock.writeLock().lock(); + try { + int result = this.recListAtoms.size(); + // represents adding () in nballerina + this.recListAtoms.add(null); + return RecAtom.createRecAtom(result); + } finally { + recListLock.writeLock().unlock(); + } + } + + public void setRecListAtomType(RecAtom rec, ListAtomicType atomicType) { + synchronized (this.recListAtoms) { + this.recListAtoms.set(rec.index(), atomicType); + } + } + + public Atom listAtom(ListAtomicType atomicType) { + return this.typeAtom(atomicType); + } + + public ListAtomicType getRecListAtomType(RecAtom ra) { + recListLock.readLock().lock(); + try { + return this.recListAtoms.get(ra.index()); + } finally { + recListLock.readLock().unlock(); + } + } + + public RecAtom recMappingAtom() { + recMapLock.writeLock().lock(); + try { + int result = this.recMappingAtoms.size(); + // represents adding () in nballerina + this.recMappingAtoms.add(null); + return RecAtom.createRecAtom(result); + } finally { + recMapLock.writeLock().unlock(); + } + } + + public void setRecMappingAtomType(RecAtom rec, MappingAtomicType atomicType) { + recMapLock.writeLock().lock(); + try { + this.recMappingAtoms.set(rec.index(), atomicType); + } finally { + recMapLock.writeLock().unlock(); + } + } + + public TypeAtom mappingAtom(MappingAtomicType atomicType) { + return this.typeAtom(atomicType); + } + + public MappingAtomicType getRecMappingAtomType(RecAtom recAtom) { + recMapLock.readLock().lock(); + try { + return this.recMappingAtoms.get(recAtom.index()); + } finally { + recMapLock.readLock().unlock(); + } + } + + public RecAtom recFunctionAtom() { + recFunctionLock.writeLock().lock(); + try { + int result = this.recFunctionAtoms.size(); + // represents adding () in nballerina + this.recFunctionAtoms.add(null); + return RecAtom.createRecAtom(result); + } finally { + recFunctionLock.writeLock().unlock(); + } + } + + public void setRecFunctionAtomType(RecAtom rec, FunctionAtomicType atomicType) { + recFunctionLock.writeLock().lock(); + try { + this.recFunctionAtoms.set(rec.index(), atomicType); + } finally { + recFunctionLock.writeLock().unlock(); + } + } + + public FunctionAtomicType getRecFunctionAtomType(RecAtom recAtom) { + recFunctionLock.readLock().lock(); + try { + return this.recFunctionAtoms.get(recAtom.index()); + } finally { + recFunctionLock.readLock().unlock(); + } + } + + public Atom functionAtom(FunctionAtomicType atomicType) { + return this.typeAtom(atomicType); + } + + private record CellSemTypeCacheKey(SemType ty, CellAtomicType.CellMutability mut) { + + } + + public int distinctAtomCountGetAndIncrement() { + return this.distinctAtomCount.getAndIncrement(); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FieldPair.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FieldPair.java new file mode 100644 index 000000000000..646310006938 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FieldPair.java @@ -0,0 +1,27 @@ +/* + * 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; + +public record FieldPair(String name, SemType type1, SemType type2, Integer index1, Integer index2) { + + public static FieldPair create(String name, SemType type1, SemType type2, Integer index1, + Integer index2) { + return new FieldPair(name, type1, type2, index1, index2); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FieldPairs.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FieldPairs.java new file mode 100644 index 000000000000..2c49b855cb38 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FieldPairs.java @@ -0,0 +1,165 @@ +/* + * 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.Common; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; + +public class FieldPairs implements Iterable { + + MappingAtomicType m1; + MappingAtomicType m2; + private final MappingPairIterator itr; + + public FieldPairs(MappingAtomicType m1, MappingAtomicType m2) { + this.m1 = m1; + this.m2 = m2; + itr = new MappingPairIterator(m1, m2); + } + + @Override + public Iterator iterator() { + return itr; + } + + private static final class MappingPairIterator implements Iterator { + + private final String[] names1; + private final String[] names2; + private final SemType[] types1; + private final SemType[] types2; + private final int len1; + private final int len2; + private int i1 = 0; + private int i2 = 0; + private final SemType rest1; + private final SemType rest2; + + private boolean doneIteration = false; + private boolean shouldCalculate = true; + private FieldPair cache = null; + + private MappingPairIterator(MappingAtomicType m1, MappingAtomicType m2) { + this.names1 = m1.names(); + this.len1 = this.names1.length; + this.types1 = m1.types(); + this.rest1 = m1.rest(); + this.names2 = m2.names(); + this.len2 = this.names2.length; + this.types2 = m2.types(); + this.rest2 = m2.rest(); + } + + @Override + public boolean hasNext() { + if (this.doneIteration) { + return false; + } + if (this.shouldCalculate) { + FieldPair cache = internalNext(); + if (cache == null) { + this.doneIteration = true; + } + this.cache = cache; + this.shouldCalculate = false; + } + return !this.doneIteration; + } + + @Override + public FieldPair next() { + if (this.doneIteration) { + throw new NoSuchElementException("Exhausted iterator"); + } + + if (this.shouldCalculate) { + FieldPair cache = internalNext(); + if (cache == null) { + // this.doneIteration = true; + throw new IllegalStateException(); + } + this.cache = cache; + } + this.shouldCalculate = true; + return this.cache; + } + + /* + * This method corresponds to `next` method of MappingPairing. + */ + private FieldPair internalNext() { + FieldPair p; + if (this.i1 >= this.len1) { + if (this.i2 >= this.len2) { + return null; + } + p = FieldPair.create(curName2(), this.rest1, curType2(), null, this.i2); + this.i2 += 1; + } else if (this.i2 >= this.len2) { + p = FieldPair.create(curName1(), curType1(), this.rest2, this.i1, null); + this.i1 += 1; + } else { + String name1 = curName1(); + String name2 = curName2(); + if (Common.codePointCompare(name1, name2)) { + p = FieldPair.create(name1, curType1(), this.rest2, this.i1, null); + this.i1 += 1; + } else if (Common.codePointCompare(name2, name1)) { + p = FieldPair.create(name2, this.rest1, curType2(), null, this.i2); + this.i2 += 1; + } else { + p = FieldPair.create(name1, curType1(), curType2(), this.i1, this.i2); + this.i1 += 1; + this.i2 += 1; + } + } + return p; + } + + private SemType curType1() { + return this.types1[this.i1]; + } + + private String curName1() { + return this.names1[this.i1]; + } + + private SemType curType2() { + return this.types2[this.i2]; + } + + private String curName2() { + return this.names2[this.i2]; + } + + public void reset() { + this.i1 = 0; + this.i2 = 0; + } + + public Optional index1(String name) { + int i1Prev = this.i1 - 1; + return i1Prev >= 0 && Objects.equals(this.names1[i1Prev], name) ? Optional.of(i1Prev) : Optional.empty(); + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FunctionAtomicType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FunctionAtomicType.java new file mode 100644 index 000000000000..423f9a7c083f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FunctionAtomicType.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.api.types.semtype; + +public record FunctionAtomicType(SemType paramType, SemType retType, SemType qualifiers) implements AtomicType { + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/LazyContainer.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/LazyContainer.java new file mode 100644 index 000000000000..9cde32b1f7ff --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/LazyContainer.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.api.types.semtype; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +class ConcurrentLazyContainer implements Supplier { + + private Supplier initializer; + private final AtomicReference value = new AtomicReference<>(); + + ConcurrentLazyContainer(Supplier initializer) { + this.initializer = initializer; + } + + @Override + public E get() { + E result = value.get(); + if (result == null) { + result = initializer.get(); + if (!value.compareAndSet(null, result)) { + result = value.get(); + } + initializer = null; + } + return result; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ListAtomicType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ListAtomicType.java new file mode 100644 index 000000000000..5210389264ed --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ListAtomicType.java @@ -0,0 +1,26 @@ +/* + * 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.FixedLengthArray; + +// TODO: move this to internal along with cell atomic type +public record ListAtomicType(FixedLengthArray members, SemType rest) implements AtomicType { + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ListProj.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ListProj.java new file mode 100644 index 000000000000..13746dfd901e --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ListProj.java @@ -0,0 +1,37 @@ +/* + * 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.BListProj; + +/** + * Wrapper utility class for list type projection. + * + * @since 2201.10.0 + */ +public final class ListProj { + + private ListProj() { + } + + public static SemType listProjInnerVal(Context cx, SemType t, SemType k) { + return BListProj.listProjInnerVal(cx, t, k); + } + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/MappingAtomicType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/MappingAtomicType.java new file mode 100644 index 000000000000..76e9804760d1 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/MappingAtomicType.java @@ -0,0 +1,47 @@ +/* + * 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.api.types.semtype; + +import java.util.ArrayList; +import java.util.Collection; + +import static io.ballerina.runtime.api.types.semtype.Core.cellInner; +import static io.ballerina.runtime.api.types.semtype.Core.intersectMemberSemTypes; +import static io.ballerina.runtime.api.types.semtype.Core.isNever; + +public record MappingAtomicType(String[] names, SemType[] types, SemType rest) implements AtomicType { + + public MappingAtomicType intersectMapping(Env env, MappingAtomicType other) { + int expectedSize = Integer.min(types().length, other.types().length); + Collection names = new ArrayList<>(expectedSize); + Collection types = new ArrayList<>(expectedSize); + for (FieldPair fieldPair : new FieldPairs(this, other)) { + names.add(fieldPair.name()); + SemType t = intersectMemberSemTypes(env, fieldPair.type1(), fieldPair.type2()); + if (isNever(cellInner(fieldPair.type1()))) { + return null; + + } + types.add(t); + } + SemType rest = intersectMemberSemTypes(env, this.rest(), other.rest()); + return new MappingAtomicType(names.toArray(String[]::new), types.toArray(SemType[]::new), rest); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/MappingProj.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/MappingProj.java new file mode 100644 index 000000000000..7ed7e47bed96 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/MappingProj.java @@ -0,0 +1,33 @@ +/* + * 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.api.types.semtype; + +import io.ballerina.runtime.internal.types.semtype.BMappingProj; + +public final class MappingProj { + + private MappingProj() { + } + + public static SemType mappingMemberTypeInnerVal(Context cx, SemType t, SemType k) { + return BMappingProj.mappingMemberTypeInnerVal(cx, t, k); + } + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Pair.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Pair.java new file mode 100644 index 000000000000..a139a2a70469 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Pair.java @@ -0,0 +1,26 @@ +/* + * 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; + +public record Pair(E1 first, E2 second) { + + public static Pair from(E1 first, E2 second) { + return new Pair<>(first, second); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/PredefinedTypeEnv.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/PredefinedTypeEnv.java new file mode 100644 index 000000000000..8d2b27fa4f18 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/PredefinedTypeEnv.java @@ -0,0 +1,585 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.internal.types.semtype.BCellSubType; +import io.ballerina.runtime.internal.types.semtype.BMappingSubType; +import io.ballerina.runtime.internal.types.semtype.FixedLengthArray; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_CELL; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_MAPPING; +import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; +import static io.ballerina.runtime.api.types.semtype.Builder.basicSubType; +import static io.ballerina.runtime.api.types.semtype.Builder.stringConst; +import static io.ballerina.runtime.api.types.semtype.Core.union; +import static io.ballerina.runtime.api.types.semtype.TypeAtom.createTypeAtom; + +final class PredefinedTypeEnv { + + private PredefinedTypeEnv() { + } + + private void initilizeEnv() { + // Initialize RecAtoms + mappingAtomicRO(); + listAtomicRO(); + mappingAtomicObjectRO(); + + // initialize atomic types + cellAtomicVal(); + cellAtomicNever(); + cellAtomicInner(); + cellAtomicInnerMapping(); + listAtomicMapping(); + cellAtomicInner(); + listAtomicMappingRO(); + cellAtomicInnerRO(); + } + + private static PredefinedTypeEnv instance; + + public static synchronized PredefinedTypeEnv getInstance() { + if (instance == null) { + instance = new PredefinedTypeEnv(); + instance.initilizeEnv(); + } + return instance; + } + + private final List> initializedCellAtoms = new ArrayList<>(); + private final List> initializedListAtoms = new ArrayList<>(); + private final List> initializedMappingAtoms = new ArrayList<>(); + private final List initializedRecListAtoms = new ArrayList<>(); + private final List initializedRecMappingAtoms = new ArrayList<>(); + private final AtomicInteger nextAtomIndex = new AtomicInteger(0); + + // This is to avoid passing down env argument when doing cell type operations. + // Please refer to the cellSubtypeDataEnsureProper() in cell.bal + private CellAtomicType cellAtomicVal; + private CellAtomicType cellAtomicNever; + + // Represent the typeAtom required to construct equivalent subtypes of map and (any|error)[]. + private CellAtomicType callAtomicInner; + + // TypeAtoms related to (map)[]. This is to avoid passing down env argument when doing + // tableSubtypeComplement operation. + private CellAtomicType cellAtomicInnerMapping; + private ListAtomicType listAtomicMapping; + + // TypeAtoms related to readonly type. This is to avoid requiring context when referring to readonly type. + // CELL_ATOMIC_INNER_MAPPING_RO & LIST_ATOMIC_MAPPING_RO are typeAtoms required to construct + // readonly & (map)[] which is then used for readonly table type when constructing VAL_READONLY + private CellAtomicType cellAtomicInnerMappingRO; + private ListAtomicType listAtomicMappingRO; + private CellAtomicType cellAtomicInnerRO; + + // TypeAtoms related to [any|error, any|error]. This is to avoid passing down env argument when doing + // streamSubtypeComplement operation. + private CellAtomicType cellAtomicUndef; + private ListAtomicType listAtomicTwoElement; + + private CellAtomicType cellAtomicObjectMember; + private CellAtomicType cellAtomicObjectMemberKind; + private CellAtomicType cellAtomicObjectMemberRO; + private CellAtomicType cellAtomicObjectMemberVisibility; + private CellAtomicType cellAtomicValRO; + private ListAtomicType listAtomicRO; + private MappingAtomicType mappingAtomicObject; + private MappingAtomicType mappingAtomicObjectMember; + private MappingAtomicType mappingAtomicObjectMemberRO; + private MappingAtomicType mappingAtomicObjectRO; + private MappingAtomicType mappingAtomicRO; + private TypeAtom atomCellInner; + private TypeAtom atomCellInnerMapping; + private TypeAtom atomCellInnerMappingRO; + private TypeAtom atomCellInnerRO; + private TypeAtom atomCellNever; + private TypeAtom atomCellObjectMember; + private TypeAtom atomCellObjectMemberKind; + private TypeAtom atomCellObjectMemberRO; + private TypeAtom atomCellObjectMemberVisibility; + private TypeAtom atomCellUndef; + private TypeAtom atomCellVal; + private TypeAtom atomCellValRO; + private TypeAtom atomListMapping; + private TypeAtom atomListMappingRO; + private TypeAtom atomMappingObject; + private TypeAtom atomMappingObjectMember; + private TypeAtom atomMappingObjectMemberRO; + + private void addInitializedCellAtom(CellAtomicType atom) { + addInitializedAtom(initializedCellAtoms, atom); + } + + private void addInitializedListAtom(ListAtomicType atom) { + addInitializedAtom(initializedListAtoms, atom); + } + + private void addInitializedMapAtom(MappingAtomicType atom) { + addInitializedAtom(initializedMappingAtoms, atom); + } + + private void addInitializedAtom(Collection> atoms, E atom) { + atoms.add(new InitializedTypeAtom<>(atom, nextAtomIndex.getAndIncrement())); + } + + private int cellAtomIndex(CellAtomicType atom) { + return atomIndex(initializedCellAtoms, atom); + } + + private int listAtomIndex(ListAtomicType atom) { + return atomIndex(initializedListAtoms, atom); + } + + private int mappingAtomIndex(MappingAtomicType atom) { + return atomIndex(initializedMappingAtoms, atom); + } + + private int atomIndex(List> initializedAtoms, E atom) { + for (InitializedTypeAtom initializedListAtom : initializedAtoms) { + if (initializedListAtom.atomicType() == atom) { + return initializedListAtom.index(); + } + } + throw new IndexOutOfBoundsException(); + } + + synchronized CellAtomicType cellAtomicVal() { + if (cellAtomicVal == null) { + cellAtomicVal = CellAtomicType.from(Builder.valType(), CellAtomicType.CellMutability.CELL_MUT_LIMITED); + addInitializedCellAtom(cellAtomicVal); + } + return cellAtomicVal; + } + + synchronized TypeAtom atomCellVal() { + if (atomCellVal == null) { + CellAtomicType cellAtomicVal = cellAtomicVal(); + atomCellVal = createTypeAtom(cellAtomIndex(cellAtomicVal), cellAtomicVal); + } + return atomCellVal; + } + + synchronized CellAtomicType cellAtomicNever() { + if (cellAtomicNever == null) { + cellAtomicNever = CellAtomicType.from(Builder.neverType(), CellAtomicType.CellMutability.CELL_MUT_LIMITED); + addInitializedCellAtom(cellAtomicNever); + } + return cellAtomicNever; + } + + synchronized TypeAtom atomCellNever() { + if (atomCellNever == null) { + CellAtomicType cellAtomicNever = cellAtomicNever(); + atomCellNever = createTypeAtom(cellAtomIndex(cellAtomicNever), cellAtomicNever); + } + return atomCellNever; + } + + synchronized CellAtomicType cellAtomicInner() { + if (callAtomicInner == null) { + callAtomicInner = CellAtomicType.from(Builder.inner(), CellAtomicType.CellMutability.CELL_MUT_LIMITED); + addInitializedCellAtom(callAtomicInner); + } + return callAtomicInner; + } + + synchronized TypeAtom atomCellInner() { + if (atomCellInner == null) { + CellAtomicType cellAtomicInner = this.cellAtomicInner(); + atomCellInner = createTypeAtom(cellAtomIndex(cellAtomicInner), cellAtomicInner); + } + return atomCellInner; + } + + synchronized CellAtomicType cellAtomicInnerMapping() { + if (cellAtomicInnerMapping == null) { + cellAtomicInnerMapping = + CellAtomicType.from(union(Builder.mappingType(), Builder.undef()), + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + addInitializedCellAtom(cellAtomicInnerMapping); + } + return cellAtomicInnerMapping; + } + + synchronized TypeAtom atomCellInnerMapping() { + if (atomCellInnerMapping == null) { + CellAtomicType cellAtomicInnerMapping = cellAtomicInnerMapping(); + atomCellInnerMapping = createTypeAtom(cellAtomIndex(cellAtomicInnerMapping), cellAtomicInnerMapping); + } + return atomCellInnerMapping; + } + + synchronized CellAtomicType cellAtomicInnerMappingRO() { + if (cellAtomicInnerMappingRO == null) { + cellAtomicInnerMappingRO = + CellAtomicType.from(union(Builder.mappingRO(), Builder.undef()), + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + addInitializedCellAtom(cellAtomicInnerMappingRO); + } + return cellAtomicInnerMappingRO; + } + + synchronized TypeAtom atomCellInnerMappingRO() { + if (atomCellInnerMappingRO == null) { + CellAtomicType cellAtomicInnerMappingRO = cellAtomicInnerMappingRO(); + atomCellInnerMappingRO = + createTypeAtom(cellAtomIndex(cellAtomicInnerMappingRO), cellAtomicInnerMappingRO); + } + return atomCellInnerMappingRO; + } + + synchronized ListAtomicType listAtomicMapping() { + if (listAtomicMapping == null) { + listAtomicMapping = new ListAtomicType( + FixedLengthArray.empty(), basicSubType( + BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellInnerMapping()))) + ); + addInitializedListAtom(listAtomicMapping); + } + return listAtomicMapping; + } + + synchronized TypeAtom atomListMapping() { + if (atomListMapping == null) { + ListAtomicType listAtomicMapping = listAtomicMapping(); + atomListMapping = createTypeAtom(listAtomIndex(listAtomicMapping), listAtomicMapping); + } + return atomListMapping; + } + + synchronized ListAtomicType listAtomicMappingRO() { + if (listAtomicMappingRO == null) { + listAtomicMappingRO = new ListAtomicType(FixedLengthArray.empty(), basicSubType( + BT_CELL, + BCellSubType.createDelegate(bddAtom(atomCellInnerMappingRO())))); + addInitializedListAtom(listAtomicMappingRO); + } + return listAtomicMappingRO; + } + + synchronized TypeAtom atomListMappingRO() { + if (atomListMappingRO == null) { + ListAtomicType listAtomicMappingRO = listAtomicMappingRO(); + atomListMappingRO = createTypeAtom(listAtomIndex(listAtomicMappingRO), listAtomicMappingRO); + } + return atomListMappingRO; + } + + synchronized CellAtomicType cellAtomicInnerRO() { + if (cellAtomicInnerRO == null) { + cellAtomicInnerRO = + CellAtomicType.from(Builder.innerReadOnly(), CellAtomicType.CellMutability.CELL_MUT_NONE); + addInitializedCellAtom(cellAtomicInnerRO); + } + return cellAtomicInnerRO; + } + + synchronized TypeAtom atomCellInnerRO() { + if (atomCellInnerRO == null) { + CellAtomicType cellAtomicInnerRO = cellAtomicInnerRO(); + atomCellInnerRO = createTypeAtom(cellAtomIndex(cellAtomicInnerRO), cellAtomicInnerRO); + } + return atomCellInnerRO; + } + + synchronized CellAtomicType cellAtomicUndef() { + if (cellAtomicUndef == null) { + cellAtomicUndef = CellAtomicType.from(Builder.undef(), CellAtomicType.CellMutability.CELL_MUT_NONE); + addInitializedCellAtom(cellAtomicUndef); + } + return cellAtomicUndef; + } + + synchronized TypeAtom atomCellUndef() { + if (atomCellUndef == null) { + CellAtomicType cellAtomicUndef = cellAtomicUndef(); + atomCellUndef = createTypeAtom(cellAtomIndex(cellAtomicUndef), cellAtomicUndef); + } + return atomCellUndef; + } + + synchronized CellAtomicType cellAtomicValRO() { + if (cellAtomicValRO == null) { + cellAtomicValRO = CellAtomicType.from( + Builder.readonlyType(), CellAtomicType.CellMutability.CELL_MUT_NONE + ); + addInitializedCellAtom(cellAtomicValRO); + } + return cellAtomicValRO; + } + + synchronized TypeAtom atomCellValRO() { + if (atomCellValRO == null) { + CellAtomicType cellAtomicValRO = cellAtomicValRO(); + atomCellValRO = createTypeAtom(cellAtomIndex(cellAtomicValRO), cellAtomicValRO); + } + return atomCellValRO; + } + + synchronized MappingAtomicType mappingAtomicObjectMemberRO() { + if (mappingAtomicObjectMemberRO == null) { + mappingAtomicObjectMemberRO = new MappingAtomicType( + new String[]{"kind", "value", "visibility"}, + new SemType[]{cellSemTypeObjectMemberKind(), cellSemTypeValRO(), + cellSemTypeObjectMemberVisibility()}, + cellSemTypeUndef()); + addInitializedMapAtom(mappingAtomicObjectMemberRO); + } + return mappingAtomicObjectMemberRO; + } + + synchronized TypeAtom atomMappingObjectMemberRO() { + if (atomMappingObjectMemberRO == null) { + MappingAtomicType mappingAtomicObjectMemberRO = mappingAtomicObjectMemberRO(); + atomMappingObjectMemberRO = createTypeAtom(mappingAtomIndex(mappingAtomicObjectMemberRO), + mappingAtomicObjectMemberRO); + } + return atomMappingObjectMemberRO; + } + + synchronized CellAtomicType cellAtomicObjectMemberRO() { + if (cellAtomicObjectMemberRO == null) { + cellAtomicObjectMemberRO = CellAtomicType.from( + mappingSemTypeObjectMemberRO(), CellAtomicType.CellMutability.CELL_MUT_NONE + ); + addInitializedCellAtom(cellAtomicObjectMemberRO); + } + return cellAtomicObjectMemberRO; + } + + synchronized TypeAtom atomCellObjectMemberRO() { + if (atomCellObjectMemberRO == null) { + CellAtomicType cellAtomicObjectMemberRO = cellAtomicObjectMemberRO(); + atomCellObjectMemberRO = createTypeAtom(cellAtomIndex(cellAtomicObjectMemberRO), cellAtomicObjectMemberRO); + } + return atomCellObjectMemberRO; + } + + synchronized CellAtomicType cellAtomicObjectMemberKind() { + if (cellAtomicObjectMemberKind == null) { + cellAtomicObjectMemberKind = CellAtomicType.from( + union(stringConst("field"), stringConst("method")), + CellAtomicType.CellMutability.CELL_MUT_NONE + ); + addInitializedCellAtom(cellAtomicObjectMemberKind); + } + return cellAtomicObjectMemberKind; + } + + synchronized TypeAtom atomCellObjectMemberKind() { + if (atomCellObjectMemberKind == null) { + CellAtomicType cellAtomicObjectMemberKind = cellAtomicObjectMemberKind(); + atomCellObjectMemberKind = + createTypeAtom(cellAtomIndex(cellAtomicObjectMemberKind), cellAtomicObjectMemberKind); + } + return atomCellObjectMemberKind; + } + + synchronized CellAtomicType cellAtomicObjectMemberVisibility() { + if (cellAtomicObjectMemberVisibility == null) { + cellAtomicObjectMemberVisibility = CellAtomicType.from( + union(stringConst("public"), stringConst("private")), + CellAtomicType.CellMutability.CELL_MUT_NONE + ); + addInitializedCellAtom(cellAtomicObjectMemberVisibility); + } + return cellAtomicObjectMemberVisibility; + } + + synchronized TypeAtom atomCellObjectMemberVisibility() { + if (atomCellObjectMemberVisibility == null) { + CellAtomicType cellAtomicObjectMemberVisibility = cellAtomicObjectMemberVisibility(); + atomCellObjectMemberVisibility = createTypeAtom(cellAtomIndex(cellAtomicObjectMemberVisibility), + cellAtomicObjectMemberVisibility); + } + return atomCellObjectMemberVisibility; + } + + synchronized MappingAtomicType mappingAtomicObjectMember() { + if (mappingAtomicObjectMember == null) { + mappingAtomicObjectMember = new MappingAtomicType( + new String[]{"kind", "value", "visibility"}, + new SemType[]{cellSemTypeObjectMemberKind(), cellSemTypeVal(), + cellSemTypeObjectMemberVisibility()}, + cellSemTypeUndef()); + ; + addInitializedMapAtom(mappingAtomicObjectMember); + } + return mappingAtomicObjectMember; + } + + synchronized TypeAtom atomMappingObjectMember() { + if (atomMappingObjectMember == null) { + MappingAtomicType mappingAtomicObjectMember = mappingAtomicObjectMember(); + atomMappingObjectMember = createTypeAtom(mappingAtomIndex(mappingAtomicObjectMember), + mappingAtomicObjectMember); + } + return atomMappingObjectMember; + } + + synchronized CellAtomicType cellAtomicObjectMember() { + if (cellAtomicObjectMember == null) { + cellAtomicObjectMember = CellAtomicType.from( + mappingSemTypeObjectMember(), CellAtomicType.CellMutability.CELL_MUT_UNLIMITED + ); + addInitializedCellAtom(cellAtomicObjectMember); + } + return cellAtomicObjectMember; + } + + synchronized TypeAtom atomCellObjectMember() { + if (atomCellObjectMember == null) { + CellAtomicType cellAtomicObjectMember = cellAtomicObjectMember(); + atomCellObjectMember = createTypeAtom(cellAtomIndex(cellAtomicObjectMember), cellAtomicObjectMember); + } + return atomCellObjectMember; + } + + synchronized MappingAtomicType mappingAtomicObject() { + if (mappingAtomicObject == null) { + mappingAtomicObject = new MappingAtomicType( + new String[]{"$qualifiers"}, new SemType[]{cellSemTypeVal()}, + cellSemTypeObjectMember() + ); + addInitializedMapAtom(mappingAtomicObject); + } + return mappingAtomicObject; + } + + synchronized TypeAtom atomMappingObject() { + if (atomMappingObject == null) { + MappingAtomicType mappingAtomicObject = mappingAtomicObject(); + atomMappingObject = createTypeAtom(mappingAtomIndex(mappingAtomicObject), mappingAtomicObject); + } + return atomMappingObject; + } + + synchronized ListAtomicType listAtomicRO() { + if (listAtomicRO == null) { + listAtomicRO = new ListAtomicType(FixedLengthArray.empty(), cellSemTypeInnerRO()); + initializedRecListAtoms.add(listAtomicRO); + } + return listAtomicRO; + } + + synchronized MappingAtomicType mappingAtomicRO() { + if (mappingAtomicRO == null) { + mappingAtomicRO = new MappingAtomicType(new String[]{}, new SemType[]{}, cellSemTypeInnerRO()); + initializedRecMappingAtoms.add(mappingAtomicRO); + } + return mappingAtomicRO; + } + + synchronized MappingAtomicType mappingAtomicObjectRO() { + if (mappingAtomicObjectRO == null) { + mappingAtomicObjectRO = new MappingAtomicType( + new String[]{"$qualifiers"}, new SemType[]{cellSemTypeVal()}, + cellSemTypeObjectMemberRO() + ); + initializedRecMappingAtoms.add(mappingAtomicObjectRO); + } + return mappingAtomicObjectRO; + } + + // Due to some reason SpotBug thinks this method is overrideable if we don't put final here as well. + final void initializeEnv(Env env) { + fillRecAtoms(env.recListAtoms, initializedRecListAtoms); + fillRecAtoms(env.recMappingAtoms, initializedRecMappingAtoms); + initializedCellAtoms.forEach(each -> env.cellAtom(each.atomicType())); + initializedListAtoms.forEach(each -> env.listAtom(each.atomicType())); + } + + private void fillRecAtoms(List envRecAtomList, List initializedRecAtoms) { + int count = reservedRecAtomCount(); + for (int i = 0; i < count; i++) { + if (i < initializedRecAtoms.size()) { + envRecAtomList.add(initializedRecAtoms.get(i)); + } else { + // This is mainly to help with bir serialization/deserialization logic. Given the number of such atoms + // will be small this shouldn't be a problem. + envRecAtomList.add(null); + } + } + } + + private int reservedRecAtomCount() { + return Integer.max(initializedRecListAtoms.size(), initializedRecMappingAtoms.size()); + } + + // TODO: avoid creating these multiple times + private SemType cellSemTypeObjectMemberKind() { + return Builder.basicSubType( + BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellObjectMemberKind())) + ); + } + + private SemType cellSemTypeValRO() { + return basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellValRO()))); + } + + private SemType cellSemTypeObjectMemberVisibility() { + return basicSubType( + BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellObjectMemberVisibility())) + ); + } + + private SemType cellSemTypeUndef() { + return basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellUndef()))); + } + + private SemType mappingSemTypeObjectMemberRO() { + return basicSubType(BT_MAPPING, BMappingSubType.createDelegate(bddAtom(atomMappingObjectMemberRO()))); + } + + private SemType cellSemTypeVal() { + return basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellVal()))); + } + + private SemType mappingSemTypeObjectMember() { + return basicSubType(BT_MAPPING, BMappingSubType.createDelegate(bddAtom(atomMappingObjectMember()))); + } + + private SemType cellSemTypeObjectMember() { + return basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellObjectMember()))); + } + + private SemType cellSemTypeObjectMemberRO() { + return basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellObjectMemberRO()))); + } + + SemType cellSemTypeInner() { + return basicSubType(BT_CELL, + BCellSubType.createDelegate(bddAtom(atomCellInner()))); + } + + private SemType cellSemTypeInnerRO() { + return basicSubType( + BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellInnerRO()))); + } + + private record InitializedTypeAtom(E atomicType, int index) { + + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/RecAtom.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/RecAtom.java new file mode 100644 index 000000000000..5e224575818f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/RecAtom.java @@ -0,0 +1,68 @@ +/* + * 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.api.types.semtype; + +/** + * Represent a recursive type atom. + * + * @since 2201.10.0 + */ +public final class RecAtom implements Atom { + + private final int index; + private static final int BDD_REC_ATOM_READONLY = 0; + private static final RecAtom ZERO = new RecAtom(BDD_REC_ATOM_READONLY); + + private RecAtom(int index) { + this.index = index; + } + + public static RecAtom createRecAtom(int index) { + if (index == BDD_REC_ATOM_READONLY) { + return ZERO; + } + return new RecAtom(index); + } + + public static RecAtom createDistinctRecAtom(int index) { + return new RecAtom(index); + } + + @Override + public int index() { + return index; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof RecAtom recAtom) { + return recAtom.index == this.index; + } + return false; + } + + @Override + public int hashCode() { + return index; + } +} 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..30514e807e8b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemType.java @@ -0,0 +1,195 @@ +/* + * 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; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_UNDEF; + +/** + * Runtime representation of SemType. + * + * @since 2201.10.0 + */ +public abstract sealed class SemType implements BasicTypeBitSet permits BSemTypeWrapper, PureSemType { + + public final int all; + public final int some; + private final SubType[] subTypeData; + private static final SubType[] EMPTY_SUBTYPE_DATA = new SubType[0]; + private Integer hashCode; + private static volatile AtomicInteger nextId = new AtomicInteger(1); + private final Integer typeID = nextId.getAndIncrement(); + private static final int CACHEABLE_TYPE_MASK = (~BasicTypeCode.BASIC_TYPE_MASK) & ((1 << (CODE_UNDEF + 1)) - 1); + private final TypeCheckResultCache resultCache; + private final boolean useCache; + + protected SemType(int all, int some, SubType[] subTypeData) { + this.all = all; + this.some = some; + this.subTypeData = subTypeData; + if ((some & CACHEABLE_TYPE_MASK) != 0) { + useCache = true; + this.resultCache = new TypeCheckResultCache(); + } else { + useCache = false; + this.resultCache = TypeCheckResultCache.EMPTY; + } + } + + protected SemType(int all) { + this(all, 0, 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; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SemType semType)) { + return false; + } + return all == semType.all && some == semType.some && Objects.deepEquals(subTypeData, semType.subTypeData); + } + + @Override + public int hashCode() { + Integer result = hashCode; + if (result == null) { + synchronized (this) { + result = hashCode; + if (result == null) { + hashCode = result = computeHashCode(); + } + } + } + return result; + } + + private int computeHashCode() { + return Objects.hash(all, some, Arrays.hashCode(subTypeData)); + } + + enum CachedResult { + TRUE, + FALSE, + NOT_FOUND + } + + CachedResult cachedSubTypeRelation(SemType other) { + if (!useCache) { + return CachedResult.NOT_FOUND; + } + int tid = other.typeID; + if (tid == typeID) { + return CachedResult.TRUE; + } + return resultCache.getCachedResult(tid); + } + + void cacheSubTypeRelation(SemType other, boolean result) { + if (useCache) { + resultCache.cacheResult(other.typeID, result); + + CachedResult cachedResult = cachedSubTypeRelation(other); + if (cachedResult != CachedResult.NOT_FOUND && + cachedResult != (result ? CachedResult.TRUE : CachedResult.FALSE)) { + throw new IllegalStateException("Inconsistent cache state"); + } + } + } + + private static sealed class TypeCheckResultCache { + + private static final TypeCheckResultCache EMPTY = new EmptyTypeCheckResultCache(); + private static final int CACHE_LIMIT = 100; + // See if we can use an identity hashmap on semtypes instead of tid + private Map cache = new HashMap<>(); + + protected void cacheResult(int tid, boolean result) { + cache.put((long) tid, result); + } + + protected CachedResult getCachedResult(int tid) { + Boolean cachedData = cache.get((long) tid); + if (cachedData == null) { + return CachedResult.NOT_FOUND; + } + return cachedData ? CachedResult.TRUE : CachedResult.FALSE; + } + } + + private static final class EmptyTypeCheckResultCache extends TypeCheckResultCache { + + @Override + public void cacheResult(int tid, boolean result) { + throw new UnsupportedOperationException("Empty cache"); + } + + @Override + public CachedResult getCachedResult(int tid) { + throw new UnsupportedOperationException("Empty cache"); + } + } + + public final SubType subTypeByCode(int code) { + if ((some() & (1 << code)) == 0) { + return null; + } + int someMask = (1 << code) - 1; + int some = some() & someMask; + return subTypeData()[Integer.bitCount(some)]; + } +} 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..2850e95e5898 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SubType.java @@ -0,0 +1,79 @@ +/* + * 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; + +import java.util.Objects; + +/** + * 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(Context cx); + + public final boolean isAll() { + return all; + } + + public final boolean isNothing() { + return nothing; + } + + public abstract SubTypeData data(); + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SubType other = (SubType) o; + return Objects.equals(data(), other.data()); + } + + @Override + public int hashCode() { + return Objects.hashCode(data()); + } + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/TypeAtom.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/TypeAtom.java new file mode 100644 index 000000000000..cbc915ccf3f0 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/TypeAtom.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.api.types.semtype; + +public record TypeAtom(int index, AtomicType atomicType) implements Atom { + + public TypeAtom { + assert atomicType != null; + } + + public static TypeAtom createTypeAtom(int index, AtomicType atomicType) { + return new TypeAtom(index, atomicType); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BCollection.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BCollection.java index a95834a70d6e..1c9d28de48da 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BCollection.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BCollection.java @@ -17,6 +17,8 @@ */ package io.ballerina.runtime.api.values; +import io.ballerina.runtime.api.types.semtype.SemType; + /** *

* {@link BCollection} represents a collection in Ballerina. @@ -32,4 +34,12 @@ public interface BCollection { * @return iterator created. */ BIterator getIterator(); + + default SemType shapeOf() { + return null; + } + + default void cacheShape(SemType semType) { + + } } 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/api/values/BValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BValue.java index 82a8bdac0a59..accc36c2bdfb 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BValue.java @@ -18,6 +18,9 @@ package io.ballerina.runtime.api.values; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; import java.util.Map; @@ -58,4 +61,8 @@ default String informalStringValue(BLink parent) { String expressionStringValue(BLink parent); Type getType(); + + default SemType widenedType(Context cx) { + return Builder.from(cx, 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..e7255ef91460 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,51 @@ 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.Env; +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.FPValue; 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 +74,14 @@ 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 +93,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 +107,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 +120,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 Map contexts = new HashMap<>(10); public static Object checkCast(Object sourceVal, Type targetType) { @@ -169,6 +150,20 @@ public static Object checkCast(Object sourceVal, Type targetType) { throw createTypeCastError(sourceVal, targetType, errors); } + private static Context context() { + // We are pinning each context to thread. This depends on the assumption physical thread is not going to + // get switched while type checking. Also for the same reason we don't need to synchronize this method. + Thread currentThread = Thread.currentThread(); + long threadID = currentThread.getId(); + Context cx = contexts.get(threadID); + if (cx != null) { + return cx; + } + cx = Context.from(Env.getInstance()); + contexts.put(threadID, cx); + return cx; + } + public static long anyToInt(Object sourceVal) { return TypeConverter.anyToIntCast(sourceVal, () -> ErrorUtils.createTypeCastError(sourceVal, TYPE_INT)); @@ -279,7 +274,23 @@ 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); + Context cx = context(); + SemType targetSemType = Builder.from(cx, targetType); + SemType targetBasicTypeUnion = Core.widenToBasicTypeUnion(targetSemType); + SemType valueBasicType = widenedType(cx, sourceVal); + if (!Core.isSubtypeSimple(valueBasicType, targetBasicTypeUnion)) { + return false; + } + if (targetBasicTypeUnion == targetSemType) { + return true; + } + SemType sourceSemType = Builder.from(cx, getType(sourceVal)); + return switch (isSubTypeInner(cx, sourceVal, sourceSemType, targetSemType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> FallbackTypeChecker.checkIsType(null, sourceVal, bTypePart(sourceSemType), + bTypePart(targetSemType)); + }; } /** @@ -292,22 +303,13 @@ 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); + Context cx = context(); + return switch (isSubType(cx, sourceVal, sourceType, targetType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> FallbackTypeChecker.checkIsType(errors, sourceVal, bTypePart(cx, sourceType), + bTypePart(cx, targetType)); + }; } /** @@ -330,7 +332,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 +351,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 +382,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 +391,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 +441,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 +458,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 +469,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,1411 +501,189 @@ 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); + Context cx = context(); + return switch (isSubType(cx, sourceType, targetType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> FallbackTypeChecker.checkIsType(bTypePart(cx, sourceType), bTypePart(cx, 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; - } - - 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 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; - } - - 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); - } + Context cx = context(); + return switch (isSubType(cx, sourceType, targetType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> FallbackTypeChecker.checkIsType(bTypePart(cx, sourceType), bTypePart(cx, targetType), + unresolvedTypes); + }; } - 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); - } + static boolean checkIsType(Object sourceVal, Type sourceType, Type targetType, List unresolvedTypes) { + Context cx = context(); + return switch (isSubType(cx, sourceVal, sourceType, targetType)) { + case TRUE -> true; + case FALSE -> false; + case MAYBE -> + FallbackTypeChecker.checkIsType(sourceVal, bTypePart(cx, sourceType), bTypePart(cx, targetType), + unresolvedTypes); + }; + } - if (sourceType == targetType || (sourceType.getTag() == targetType.getTag() && sourceType.equals(targetType))) { - return true; - } + /** + * 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; + } - if (targetType.isReadOnly() && !sourceType.isReadOnly()) { - return false; - } + public static boolean isNumericType(Type type) { + type = getImpliedType(type); + return type.getTag() < TypeTags.STRING_TAG || TypeTags.isIntegerTypeTag(type.getTag()); + } - 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); - } + static boolean isByteLiteral(long longValue) { + return (longValue >= BBYTE_MIN_VALUE && longValue <= BBYTE_MAX_VALUE); } // Private methods - private static boolean checkTypeDescType(Type sourceType, BTypedescType targetType, - List unresolvedTypes) { - if (sourceType.getTag() != TypeTags.TYPEDESC_TAG) { - return false; - } - - BTypedescType sourceTypedesc = (BTypedescType) sourceType; - return checkIsType(sourceTypedesc.getConstraint(), targetType.getConstraint(), unresolvedTypes); - } - - 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); - 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; - } + private enum TypeCheckResult { + TRUE, + FALSE, + MAYBE } - private 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 (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 TypeCheckResult isSubType(Context cx, Object sourceValue, Type source, Type target) { + TypeCheckResult result = isSubType(cx, source, target); + if (result != TypeCheckResult.FALSE) { + return result; } + return isSubTypeWithShape(cx, sourceValue, Builder.from(cx, source), Builder.from(cx, target)); } - private static boolean isFiniteTypeMatch(BFiniteType sourceType, Type targetType) { - for (Object bValue : sourceType.valueSpace) { - if (!checkIsType(bValue, targetType)) { - return false; + private static TypeCheckResult isSubTypeWithShape(Context cx, Object sourceValue, SemType source, SemType target) { + TypeCheckResult result; + result = isSubTypeWithShapeInner(cx, sourceValue, target); + if (result == TypeCheckResult.MAYBE) { + if (Core.containsBasicType(source, B_TYPE_TOP)) { + return TypeCheckResult.MAYBE; } + return TypeCheckResult.FALSE; } - return true; + return result; } - private static boolean isUnionTypeMatch(BUnionType sourceType, Type targetType, List unresolvedTypes) { - for (Type type : sourceType.getMemberTypes()) { - if (!checkIsType(type, targetType, unresolvedTypes)) { - return false; + private static TypeCheckResult isSubType(Context cx, Type source, Type target) { + if (source instanceof ParameterizedType sourceParamType) { + if (target instanceof ParameterizedType targetParamType) { + return isSubType(cx, sourceParamType.getParamValueType(), targetParamType.getParamValueType()); } + return isSubType(cx, sourceParamType.getParamValueType(), target); } - 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; - - } + return isSubTypeInner(Builder.from(cx, source), Builder.from(cx, target)); } - 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 TypeCheckResult isSubTypeInner(Context cx, Object sourceValue, SemType source, SemType target) { + TypeCheckResult result = isSubTypeInner(source, target); + if (result != TypeCheckResult.FALSE) { + return result; } + return isSubTypeWithShape(cx, sourceValue, source, target); } - 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 TypeCheckResult isSubTypeWithShapeInner(Context cx, Object sourceValue, SemType target) { + Optional sourceSingletonType = Builder.shapeOf(cx, sourceValue); + if (sourceSingletonType.isEmpty()) { + return fallbackToBTypeWithoutShape(sourceValue, target) ? + TypeCheckResult.MAYBE : TypeCheckResult.FALSE; } + SemType singletonType = sourceSingletonType.get(); + return isSubTypeInner(singletonType, target); } - 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; + private static boolean fallbackToBTypeWithoutShape(Object sourceValue, SemType target) { + if (!Core.containsBasicType(target, B_TYPE_TOP)) { + return false; } - - return checkIsType(sourceType.restFieldType, targetConstrainedType, unresolvedTypes); + return !(sourceValue instanceof FPValue); } - 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); + private static TypeCheckResult isSubTypeInner(SemType source, SemType target) { + Context cx = context(); + if (!Core.containsBasicType(source, B_TYPE_TOP)) { + return Core.isSubType(cx, source, target) ? TypeCheckResult.TRUE : TypeCheckResult.FALSE; } - - 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; + if (!Core.containsBasicType(target, B_TYPE_TOP)) { + if (Core.containsBasicType(source, Builder.objectType())) { + // This is a hack but since target defines the minimal it is fine + SemType sourcePureSemType = Core.intersect(source, SEMTYPE_TOP); + return Core.isSubType(cx, sourcePureSemType, target) ? TypeCheckResult.TRUE : TypeCheckResult.FALSE; } - return checkIsType(source.constraint, targetConstraint, unresolvedTypes); + return TypeCheckResult.FALSE; } - if (TypeTags.isXMLTypeTag(sourceTag)) { - return checkIsType(sourceType, target.constraint, unresolvedTypes); - } - return 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; } - 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); + private static SemType widenedType(Context cx, 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 ((BValue) value).widenedType(cx); } - 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 BType bTypePart(Context cx, Type t) { + return bTypePart(Builder.from(cx, t)); } - 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 BType bTypePart(SemType t) { + return (BType) Core.subTypeData(t, BasicTypeCode.BT_B_TYPE); } - private static boolean checkIsTableType(Type sourceType, BTableType targetType, List unresolvedTypes) { + public static boolean isInherentlyImmutableType(Type sourceType) { 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) { + if (FallbackTypeChecker.isSimpleBasicType(sourceType)) { return true; } - if (targetType.getKeyType() != null) { - if (srcTableType.getKeyType() != null && - (checkConstraints(srcTableType.getKeyType(), targetType.getKeyType(), unresolvedTypes))) { + 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; - } - - 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: + 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; } } @@ -1998,514 +723,63 @@ public static boolean isSelectivelyImmutableType(Type type, Set unresolved 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); + BRecordType recordType = (BRecordType) type; + for (Field field : recordType.getFields().values()) { + Type fieldType = field.getFieldType(); + if (!isInherentlyImmutableType(fieldType) && + !isSelectivelyImmutableType(fieldType, unresolvedTypes)) { + return false; + } + } - XmlNodeType xmlSourceNodeType = xmlSource.getNodeType(); - if (xmlSourceNodeType != XmlNodeType.SEQUENCE) { - return acceptedNodeTypes.contains(xmlSourceNodeType); - } + Type recordRestType = recordType.restFieldType; + if (recordRestType == null) { + return true; + } - XmlSequence seq = (XmlSequence) xmlSource; - for (BXml m : seq.getChildrenList()) { - if (!acceptedNodeTypes.contains(m.getNodeType())) { - return false; - } - } - return true; - } + return isInherentlyImmutableType(recordRestType) || + isSelectivelyImmutableType(recordRestType, unresolvedTypes); + case TypeTags.OBJECT_TYPE_TAG: + BObjectType objectType = (BObjectType) type; - public static boolean isNumericType(Type type) { - type = getImpliedType(type); - return type.getTag() < TypeTags.STRING_TAG || TypeTags.isIntegerTypeTag(type.getTag()); - } + if (SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.CLASS) && + !SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.READONLY)) { + return false; + } - private static boolean checkIsLikeAnydataType(Object sourceValue, Type sourceType, - List unresolvedValues, - boolean allowNumericConversion) { - sourceType = getImpliedType(sourceType); - switch (sourceType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: + 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 isLikeAnydataType(((MapValueImpl) sourceValue).values().toArray(), - unresolvedValues, allowNumericConversion); + Type constraintType = ((BMapType) type).getConstrainedType(); + return isInherentlyImmutableType(constraintType) || + isSelectivelyImmutableType(constraintType, unresolvedTypes); 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); + 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; + } } - case TypeTags.TUPLE_TAG: - return isLikeAnydataType(((ArrayValue) sourceValue).getValues(), unresolvedValues, - allowNumericConversion); + 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 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) { @@ -2550,379 +824,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 +956,7 @@ static boolean isStructuredType(Type type) { * * @since 0.995.0 */ - private static class TypePair { + static class TypePair { Type sourceType; Type targetType; @@ -3345,7 +1246,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..307b57df3402 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,8 @@ 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.Context; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.RefValue; import java.util.Optional; @@ -99,4 +101,9 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + SemType createSemType(Context cx) { + return BTypeConverter.fromAnyType(this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BArrayType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BArrayType.java index 5b5344a730c1..7e2f32b1c2fe 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BArrayType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BArrayType.java @@ -22,13 +22,24 @@ import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; import io.ballerina.runtime.internal.values.ArrayValue; import io.ballerina.runtime.internal.values.ArrayValueImpl; import io.ballerina.runtime.internal.values.ReadOnlyUtils; import java.util.Optional; +import static io.ballerina.runtime.api.types.semtype.Builder.neverType; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; + /** * {@code BArrayType} represents a type of an arrays in Ballerina. *

@@ -40,7 +51,9 @@ * @since 0.995.0 */ @SuppressWarnings("unchecked") -public class BArrayType extends BType implements ArrayType { +public class BArrayType extends BType implements ArrayType, PartialSemTypeSupplier, TypeWithShape { + + private static final SemType[] EMPTY_SEMTYPE_ARR = new SemType[0]; private Type elementType; private int dimensions = 1; private int size = -1; @@ -51,6 +64,8 @@ public class BArrayType extends BType implements ArrayType { private IntersectionType immutableType; private IntersectionType intersectionType = null; private int typeFlags; + private ListDefinition defn; + private final Env env = Env.getInstance(); public BArrayType(Type elementType) { this(elementType, false); } @@ -87,6 +102,8 @@ public BArrayType(int typeFlags, int size, boolean readonly, boolean hasFillerVa public void setElementType(Type elementType, int dimensions, boolean elementRO) { this.elementType = readonly && !elementRO ? ReadOnlyUtils.getReadOnlyType(elementType) : elementType; this.dimensions = dimensions; + defn = null; + resetSemTypeCache(); } private void setFlagsBasedOnElementType() { @@ -201,4 +218,65 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + synchronized SemType createSemType(Context cx) { + if (defn != null) { + return defn.getSemType(env); + } + defn = new ListDefinition(); + SemType elementType = Builder.from(cx, getElementType()); + SemType pureBTypePart = Core.intersect(elementType, Core.B_TYPE_TOP); + if (!Core.isNever(pureBTypePart)) { + cx.markProvisionTypeReset(); + SemType pureSemTypePart = Core.intersect(elementType, Core.SEMTYPE_TOP); + SemType semTypePart = getSemTypePart(pureSemTypePart); + SemType bTypePart = BTypeConverter.wrapAsPureBType(this); + return Core.union(semTypePart, bTypePart); + } + + return getSemTypePart(elementType); + } + + private SemType getSemTypePart(SemType elementType) { + CellAtomicType.CellMutability mut = isReadOnly() ? CellAtomicType.CellMutability.CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + if (size == -1) { + return defn.defineListTypeWrapped(env, EMPTY_SEMTYPE_ARR, 0, elementType, mut); + } else { + SemType[] initial = {elementType}; + return defn.defineListTypeWrapped(env, initial, size, neverType(), mut); + } + } + + @Override + public void resetSemTypeCache() { + super.resetSemTypeCache(); + defn = null; + } + + @Override + public Optional shapeOf(Context cx, Object object) { + if (!isReadOnly()) { + return Optional.of(get(cx)); + } + BArray value = (BArray) object; + SemType cachedShape = value.shapeOf(); + if (cachedShape != null) { + return Optional.of(cachedShape); + } + int size = value.size(); + SemType[] memberTypes = new SemType[size]; + for (int i = 0; i < size; i++) { + Optional memberType = Builder.shapeOf(cx, value.get(i)); + if (memberType.isEmpty()) { + return Optional.empty(); + } + memberTypes[i] = memberType.get(); + } + ListDefinition ld = new ListDefinition(); + SemType semType = ld.defineListTypeWrapped(env, memberTypes, memberTypes.length, neverType(), CELL_MUT_NONE); + value.cacheShape(semType); + return Optional.of(semType); + } } 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/BErrorType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BErrorType.java index d01821fae7a9..6bba6a9b1038 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BErrorType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BErrorType.java @@ -24,20 +24,31 @@ import io.ballerina.runtime.api.types.ErrorType; import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; +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.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.internal.types.semtype.ErrorUtils; import io.ballerina.runtime.internal.values.ErrorValue; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; /** * {@code BErrorType} represents error type in Ballerina. * * @since 0.995.0 */ -public class BErrorType extends BAnnotatableType implements ErrorType { +public class BErrorType extends BAnnotatableType implements ErrorType, PartialSemTypeSupplier, TypeWithShape { - public Type detailType = PredefinedTypes.TYPE_ERROR_DETAIL; + public Type detailType = PredefinedTypes.TYPE_DETAIL; public BTypeIdSet typeIdSet; private IntersectionType intersectionType = null; + private DistinctIdSupplier distinctIdSupplier; + private static final AtomicInteger nextId = new AtomicInteger(0); + private final int id = nextId.getAndIncrement(); public BErrorType(String typeName, Module pkg, Type detailType) { super(typeName, pkg, ErrorValue.class); @@ -50,6 +61,7 @@ public BErrorType(String typeName, Module pkg) { public void setTypeIdSet(BTypeIdSet typeIdSet) { this.typeIdSet = typeIdSet; + this.distinctIdSupplier = null; } @Override @@ -113,4 +125,57 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + synchronized SemType createSemType(Context cx) { + boolean hasBType = false; + SemType err; + if (detailType == null || isTopType()) { + err = Builder.errorType(); + hasBType = true; + } else { + SemType detailType = Builder.from(cx, getDetailType()); + if (!Core.isNever(Core.intersect(detailType, Core.B_TYPE_TOP))) { + hasBType = true; + detailType = Core.intersect(detailType, Core.SEMTYPE_TOP); + } + err = ErrorUtils.errorDetail(detailType); + } + + if (distinctIdSupplier == null) { + distinctIdSupplier = new DistinctIdSupplier(cx.env, getTypeIdSet()); + } + SemType pureSemType = + distinctIdSupplier.get().stream().map(ErrorUtils::errorDistinct).reduce(err, Core::intersect); + if (hasBType) { + return Core.union(pureSemType, BTypeConverter.wrapAsPureBType(this)); + } + return pureSemType; + } + + private boolean isTopType() { + return detailType == PredefinedTypes.TYPE_DETAIL; + } + + @Override + public Optional shapeOf(Context cx, Object object) { + BError errorValue = (BError) object; + Object details = errorValue.getDetails(); + if (!(details instanceof BMap errorDetails)) { + return Optional.empty(); + } + SemType detailType = Builder.from(cx, errorDetails.getType()); + boolean hasBType = !Core.isNever(Core.intersect(detailType, Core.B_TYPE_TOP)); + return BMapType.readonlyShape(cx, errorDetails) + .map(ErrorUtils::errorDetail) + .map(err -> distinctIdSupplier.get().stream().map(ErrorUtils::errorDistinct) + .reduce(err, Core::intersect)) + .map(semType -> { + if (hasBType) { + return Core.union(semType, BTypeConverter.wrapAsPureBType(this)); + } else { + return semType; + } + }); + } } 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..d848c47d2dbb 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,8 @@ 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.Context; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.values.RefValue; @@ -57,6 +59,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 +202,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(Context cx) { + return BTypeConverter.fromFiniteType(cx, 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/BFunctionType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFunctionType.java index 87dd2924dacd..557550a8a8bb 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFunctionType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFunctionType.java @@ -25,6 +25,15 @@ import io.ballerina.runtime.api.types.FunctionType; import io.ballerina.runtime.api.types.Parameter; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.internal.types.semtype.FunctionDefinition; +import io.ballerina.runtime.internal.types.semtype.FunctionQualifiers; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; import java.util.Arrays; @@ -33,12 +42,16 @@ * * @since 0.995.0 */ -public class BFunctionType extends BAnnotatableType implements FunctionType { +public class BFunctionType extends BAnnotatableType implements FunctionType, PartialSemTypeSupplier { public Type restType; public Type retType; public long flags; public Parameter[] parameters; + private static final Env env = Env.getInstance(); + private static final SemType ISOLATED_TOP = createIsolatedTop(env); + + private FunctionDefinition defn; public BFunctionType(Module pkg) { super("function ()", pkg, Object.class); @@ -218,4 +231,93 @@ public Type getReturnType() { public long getFlags() { return flags; } + + private static SemType createIsolatedTop(Env env) { + FunctionDefinition fd = new FunctionDefinition(); + SemType ret = Builder.valType(); + return fd.define(env, Builder.neverType(), ret, FunctionQualifiers.create(true, false)); + } + + @Override + synchronized SemType createSemType(Context cx) { + if (isFunctionTop()) { + SemType topType = getTopType(); + return Core.union(topType, BTypeConverter.wrapAsPureBType(this)); + } + if (defn != null) { + return defn.getSemType(env); + } + FunctionDefinition fd = new FunctionDefinition(); + this.defn = fd; + SemType[] params = new SemType[parameters.length]; + boolean hasBType = false; + for (int i = 0; i < parameters.length; i++) { + var result = getSemType(cx, parameters[i].type); + hasBType = hasBType || result.hasBTypePart; + params[i] = result.pureSemTypePart; + } + SemType rest; + if (restType instanceof BArrayType arrayType) { + var result = getSemType(cx, arrayType.getElementType()); + hasBType = hasBType || result.hasBTypePart; + rest = result.pureSemTypePart; + } else { + rest = Builder.neverType(); + } + + SemType returnType; + if (retType != null) { + var result = getSemType(cx, retType); + hasBType = hasBType || result.hasBTypePart; + returnType = result.pureSemTypePart; + } else { + returnType = Builder.nilType(); + } + ListDefinition paramListDefinition = new ListDefinition(); + SemType paramType = paramListDefinition.defineListTypeWrapped(env, params, params.length, rest, + CellAtomicType.CellMutability.CELL_MUT_NONE); + SemType result = fd.define(env, paramType, returnType, getQualifiers()); + if (hasBType) { + cx.markProvisionTypeReset(); + SemType bTypePart = BTypeConverter.wrapAsPureBType(this); + return Core.union(result, bTypePart); + } + return result; + } + + private SemType getTopType() { + if (SymbolFlags.isFlagOn(flags, SymbolFlags.ISOLATED)) { + return ISOLATED_TOP; + } + return Builder.functionType(); + } + + private record SemTypeResult(boolean hasBTypePart, SemType pureSemTypePart) { + + } + + @Override + public FunctionQualifiers getQualifiers() { + return FunctionQualifiers.create(SymbolFlags.isFlagOn(flags, SymbolFlags.ISOLATED), + SymbolFlags.isFlagOn(flags, SymbolFlags.TRANSACTIONAL)); + } + + // TODO: consider moving this to builder + private static SemTypeResult getSemType(Context cx, Type type) { + SemType semType = Builder.from(cx, type); + if (!Core.isNever(Core.intersect(semType, Core.B_TYPE_TOP))) { + return new SemTypeResult(true, Core.intersect(semType, Core.SEMTYPE_TOP)); + } + return new SemTypeResult(false, semType); + } + + private boolean isFunctionTop() { + return parameters == null && restType == null && retType == null; + } + + @Override + public void resetSemTypeCache() { + super.resetSemTypeCache(); + defn = null; + } } 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..5f96c1e1f040 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,10 @@ 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.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 java.util.ArrayList; import java.util.Arrays; @@ -37,7 +41,7 @@ * * @since 2.0.0 */ -public class BIntersectionType extends BType implements IntersectionType { +public class BIntersectionType extends BType implements IntersectionType, TypeWithShape { private static final String PADDED_AMPERSAND = " & "; private static final String OPENING_PARENTHESIS = "("; @@ -215,4 +219,36 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + SemType createSemType(Context cx) { + Type effectiveType = getEffectiveType(); + if (effectiveType instanceof SemType semType) { + return semType; + } + if (constituentTypes.isEmpty()) { + return Builder.neverType(); + } + SemType result = Builder.from(cx, constituentTypes.get(0)); + boolean hasBType = Core.containsBasicType(Builder.from(cx, effectiveType), Builder.bType()); + result = Core.intersect(result, Core.SEMTYPE_TOP); + for (int i = 1; i < constituentTypes.size(); i++) { + SemType memberType = Builder.from(cx, constituentTypes.get(i)); +// hasBType |= Core.containsBasicType(memberType, Builder.bType()); + result = Core.intersect(result, memberType); + } + if (hasBType) { + return Core.union(result, BTypeConverter.wrapAsPureBType((BType) effectiveType)); + } + return result; + } + + @Override + public Optional shapeOf(Context cx, Object object) { + Type effectiveType = getEffectiveType(); + if (effectiveType instanceof TypeWithShape typeWithShape) { + return typeWithShape.shapeOf(cx, object); + } + return Optional.empty(); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMapType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMapType.java index 2ad5a6b78db0..30706bfcb341 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMapType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMapType.java @@ -24,12 +24,23 @@ import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.internal.types.semtype.MappingDefinition; import io.ballerina.runtime.internal.values.MapValueImpl; import io.ballerina.runtime.internal.values.ReadOnlyUtils; +import java.util.Map; import java.util.Optional; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; + /** * {@code BMapType} represents a type of a map in Ballerina. *

@@ -41,12 +52,15 @@ * @since 0.995.0 */ @SuppressWarnings("unchecked") -public class BMapType extends BType implements MapType { +public class BMapType extends BType implements MapType, PartialSemTypeSupplier, TypeWithShape { + public static final MappingDefinition.Field[] EMPTY_FIELD_ARR = new MappingDefinition.Field[0]; private final Type constraint; private final boolean readonly; private IntersectionType immutableType; private IntersectionType intersectionType = null; + private MappingDefinition defn; + private final Env env = Env.getInstance(); public BMapType(Type constraint) { this(constraint, false); @@ -170,4 +184,65 @@ public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + @Override + synchronized SemType createSemType(Context cx) { + if (defn != null) { + return defn.getSemType(env); + } + defn = new MappingDefinition(); + SemType restType = Builder.from(cx, getConstrainedType()); + SemType pureBTypePart = Core.intersect(restType, Core.B_TYPE_TOP); + if (!Core.isNever(pureBTypePart)) { + cx.markProvisionTypeReset(); + SemType pureSemTypePart = Core.intersect(restType, Core.SEMTYPE_TOP); + SemType semTypePart = getSemTypePart(pureSemTypePart); + SemType bTypePart = BTypeConverter.wrapAsPureBType(this); + return Core.union(semTypePart, bTypePart); + } + return getSemTypePart(restType); + } + + @Override + public void resetSemTypeCache() { + super.resetSemTypeCache(); + defn = null; + } + + @Override + public Optional shapeOf(Context cx, Object object) { + if (!isReadOnly()) { + return Optional.of(get(cx)); + } + BMap value = (BMap) object; + SemType cachedShape = value.shapeOf(); + if (cachedShape != null) { + return Optional.of(cachedShape); + } + + return readonlyShape(cx, value); + } + + static Optional readonlyShape(Context cx, BMap value) { + int nFields = value.size(); + MappingDefinition.Field[] fields = new MappingDefinition.Field[nFields]; + Map.Entry[] entries = (Map.Entry[]) value.entrySet().toArray(Map.Entry[]::new); + for (int i = 0; i < nFields; i++) { + Optional valueType = Builder.shapeOf(cx, entries[i].getValue()); + if (valueType.isEmpty()) { + return Optional.empty(); + } + SemType fieldType = valueType.get(); + fields[i] = new MappingDefinition.Field(entries[i].getKey().toString(), fieldType, true, false); + } + MappingDefinition md = new MappingDefinition(); + SemType semType = md.defineMappingTypeWrapped(cx.env, fields, Builder.neverType(), CELL_MUT_NONE); + value.cacheShape(semType); + return Optional.of(semType); + } + + private SemType getSemTypePart(SemType restType) { + CellAtomicType.CellMutability mut = isReadOnly() ? CellAtomicType.CellMutability.CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + return defn.defineMappingTypeWrapped(env, EMPTY_FIELD_ARR, restType, mut); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMethodType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMethodType.java index 360829a47f11..a3b830c51d36 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMethodType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMethodType.java @@ -83,4 +83,9 @@ public MethodType duplicate() { public boolean isIsolated() { return SymbolFlags.isFlagOn(flags, SymbolFlags.ISOLATED); } + + @Override + public String name() { + return funcName; + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNetworkObjectType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNetworkObjectType.java index a0666927a833..774964095558 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNetworkObjectType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNetworkObjectType.java @@ -22,8 +22,12 @@ import io.ballerina.runtime.api.types.NetworkObjectType; import io.ballerina.runtime.api.types.RemoteMethodType; import io.ballerina.runtime.api.types.ResourceMethodType; +import io.ballerina.runtime.api.types.semtype.Context; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Stream; /** * {@code BNetworkObjectType} represents a network object in Ballerina. @@ -80,4 +84,15 @@ private RemoteMethodType[] getRemoteMethods(MethodType[] methodTypes) { public ResourceMethodType[] getResourceMethods() { return resourceMethods; } + + @Override + protected Collection allMethods(Context cx) { + Stream methodStream = Arrays.stream(getMethods()).map(method -> MethodData.fromMethod(cx, method)); + Stream remoteMethodStream = + Arrays.stream(getRemoteMethods()).map(method -> MethodData.fromMethod(cx, method)); + Stream resoucrMethodStream = + Arrays.stream(getResourceMethods()).map(method -> MethodData.fromResourceMethod(cx, + (BResourceMethodType) method)); + return Stream.concat(methodStream, Stream.concat(remoteMethodStream, resoucrMethodStream)).toList(); + } } 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..9fc0bd7e38cf 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,16 @@ 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.Context; +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 +38,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(Context cx) { + return Builder.nilType(); + } } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BObjectType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BObjectType.java index c3ee14297571..34891b633bf7 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BObjectType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BObjectType.java @@ -23,23 +23,43 @@ import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.flags.SymbolFlags; 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.ObjectType; +import io.ballerina.runtime.api.types.Parameter; import io.ballerina.runtime.api.types.ResourceMethodType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeIdSet; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.internal.ValueUtils; import io.ballerina.runtime.internal.scheduling.Scheduler; import io.ballerina.runtime.internal.scheduling.Strand; +import io.ballerina.runtime.internal.types.semtype.FunctionDefinition; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; +import io.ballerina.runtime.internal.types.semtype.Member; +import io.ballerina.runtime.internal.types.semtype.ObjectDefinition; +import io.ballerina.runtime.internal.types.semtype.ObjectQualifiers; +import io.ballerina.runtime.internal.values.AbstractObjectValue; import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.Set; import java.util.StringJoiner; import static io.ballerina.runtime.api.TypeTags.SERVICE_TAG; @@ -49,7 +69,7 @@ * * @since 0.995.0 */ -public class BObjectType extends BStructureType implements ObjectType { +public class BObjectType extends BStructureType implements ObjectType, PartialSemTypeSupplier, TypeWithShape { private MethodType[] methodTypes; private MethodType initMethod; @@ -62,6 +82,11 @@ public class BObjectType extends BStructureType implements ObjectType { private String cachedToString; private boolean resolving; + private ObjectDefinition od; + private final Env env = Env.getInstance(); + // TODO: better name + private SemType softSemTypeCache; + private DistinctIdSupplier distinctIdSupplier; /** * Create a {@code BObjectType} which represents the user defined struct type. @@ -214,6 +239,7 @@ public void setIntersectionType(IntersectionType intersectionType) { public void setTypeIdSet(BTypeIdSet typeIdSet) { this.typeIdSet = typeIdSet; + this.distinctIdSupplier = null; } public BObjectType duplicate() { @@ -244,4 +270,247 @@ public boolean hasAnnotations() { public TypeIdSet getTypeIdSet() { return new BTypeIdSet(new ArrayList<>(typeIdSet.ids)); } + + @Override + synchronized SemType createSemType(Context cx) { + if (distinctIdSupplier == null) { + distinctIdSupplier = new DistinctIdSupplier(env, typeIdSet); + } + return distinctIdSupplier.get().stream().map(ObjectDefinition::distinct) + .reduce(semTypeInner(cx), Core::intersect); + } + + private static boolean skipField(Set seen, String name) { + if (name.startsWith("$")) { + return true; + } + return !seen.add(name); + } + + private SemType semTypeInner(Context cx) { + if (softSemTypeCache != null) { + return softSemTypeCache; + } + if (od != null) { + return od.getSemType(env); + } + od = new ObjectDefinition(); + ObjectQualifiers qualifiers = getObjectQualifiers(); + List members = new ArrayList<>(); + boolean hasBTypes = false; + Set seen = new HashSet<>(fields.size() + methodTypes.length); + for (Entry entry : fields.entrySet()) { + String name = entry.getKey(); + if (skipField(seen, name)) { + continue; + } + Field field = entry.getValue(); + boolean isPublic = SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.PUBLIC); + boolean isImmutable = qualifiers.readonly() | SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY); + SemType ty = Builder.from(cx, field.getFieldType()); + SemType pureBTypePart = Core.intersect(ty, Core.B_TYPE_TOP); + if (!Core.isNever(pureBTypePart)) { + hasBTypes = true; + ty = Core.intersect(ty, Core.SEMTYPE_TOP); + } + members.add(new Member(name, ty, Member.Kind.Field, + isPublic ? Member.Visibility.Public : Member.Visibility.Private, isImmutable)); + } + for (MethodData method : allMethods(cx)) { + String name = method.name(); + if (skipField(seen, name)) { + continue; + } + boolean isPublic = SymbolFlags.isFlagOn(method.flags(), SymbolFlags.PUBLIC); + SemType semType = method.semType(); + SemType pureBTypePart = Core.intersect(semType, Core.B_TYPE_TOP); + if (!Core.isNever(pureBTypePart)) { + hasBTypes = true; + semType = Core.intersect(semType, Core.SEMTYPE_TOP); + } + members.add(new Member(name, semType, Member.Kind.Method, + isPublic ? Member.Visibility.Public : Member.Visibility.Private, true)); + } + SemType semTypePart = od.define(env, qualifiers, members); + if (hasBTypes || members.isEmpty()) { + cx.markProvisionTypeReset(); + SemType bTypePart = BTypeConverter.wrapAsPureBType(this); + softSemTypeCache = Core.union(semTypePart, bTypePart); + return softSemTypeCache; + } + return semTypePart; + } + + private ObjectQualifiers getObjectQualifiers() { + boolean isolated = SymbolFlags.isFlagOn(getFlags(), SymbolFlags.ISOLATED); + boolean readonly = SymbolFlags.isFlagOn(getFlags(), SymbolFlags.READONLY); + ObjectQualifiers.NetworkQualifier networkQualifier; + if (SymbolFlags.isFlagOn(getFlags(), SymbolFlags.SERVICE)) { + networkQualifier = ObjectQualifiers.NetworkQualifier.Service; + } else if (SymbolFlags.isFlagOn(getFlags(), SymbolFlags.CLIENT)) { + networkQualifier = ObjectQualifiers.NetworkQualifier.Client; + } else { + networkQualifier = ObjectQualifiers.NetworkQualifier.None; + } + return new ObjectQualifiers(isolated, readonly, networkQualifier); + } + + @Override + public synchronized Optional shapeOf(Context cx, Object object) { + AbstractObjectValue abstractObjectValue = (AbstractObjectValue) object; + SemType cachedShape = abstractObjectValue.shapeOf(); + if (cachedShape != null) { + return Optional.of(cachedShape); + } + if (distinctIdSupplier == null) { + distinctIdSupplier = new DistinctIdSupplier(env, typeIdSet); + } + SemType shape = distinctIdSupplier.get().stream().map(ObjectDefinition::distinct).reduce( + valueShape(cx, abstractObjectValue), Core::intersect); + abstractObjectValue.cacheShape(shape); + return Optional.of(shape); + } + + private SemType valueShape(Context cx, AbstractObjectValue object) { + ObjectDefinition od = new ObjectDefinition(); + List members = new ArrayList<>(); + Set seen = new HashSet<>(fields.size() + methodTypes.length); + ObjectQualifiers qualifiers = getObjectQualifiers(); + boolean hasBTypes = false; + for (Entry entry : fields.entrySet()) { + String name = entry.getKey(); + if (skipField(seen, name)) { + continue; + } + Field field = entry.getValue(); + boolean isPublic = SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.PUBLIC); + boolean isImmutable = qualifiers.readonly() | SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY) | + SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.FINAL); + SemType ty = fieldShape(cx, field, object, isImmutable); + SemType pureBTypePart = Core.intersect(ty, Core.B_TYPE_TOP); + if (!Core.isNever(pureBTypePart)) { + hasBTypes = true; + ty = Core.intersect(ty, Core.SEMTYPE_TOP); + } + members.add(new Member(name, ty, Member.Kind.Field, + isPublic ? Member.Visibility.Public : Member.Visibility.Private, isImmutable)); + } + for (MethodData method : allMethods(cx)) { + String name = method.name(); + if (skipField(seen, name)) { + continue; + } + boolean isPublic = SymbolFlags.isFlagOn(method.flags(), SymbolFlags.PUBLIC); + SemType semType = method.semType(); + SemType pureBTypePart = Core.intersect(semType, Core.B_TYPE_TOP); + if (!Core.isNever(pureBTypePart)) { + hasBTypes = true; + semType = Core.intersect(semType, Core.SEMTYPE_TOP); + } + members.add(new Member(name, semType, Member.Kind.Method, + isPublic ? Member.Visibility.Public : Member.Visibility.Private, true)); + } + SemType semTypePart = od.define(env, qualifiers, members); + if (hasBTypes) { + SemType bTypePart = BTypeConverter.wrapAsPureBType(this); + return Core.union(semTypePart, bTypePart); + } + return semTypePart; + } + + private static SemType fieldShape(Context cx, Field field, AbstractObjectValue objectValue, boolean isImmutable) { + if (!isImmutable) { + return Builder.from(cx, field.getFieldType()); + } + BString fieldName = StringUtils.fromString(field.getFieldName()); + Optional shape = Builder.shapeOf(cx, objectValue.get(fieldName)); + assert !shape.isEmpty(); + return shape.get(); + } + + @Override + public void resetSemTypeCache() { + super.resetSemTypeCache(); + od = null; + } + + protected Collection allMethods(Context cx) { + return Arrays.stream(methodTypes).map(method -> MethodData.fromMethod(cx, method)).toList(); + } + + protected record MethodData(String name, long flags, SemType semType) { + + static MethodData fromMethod(Context cx, MethodType method) { + return new MethodData(method.getName(), method.getFlags(), + Builder.from(cx, method.getType())); + } + + static MethodData fromResourceMethod(Context cx, BResourceMethodType method) { + StringBuilder sb = new StringBuilder(); + sb.append(method.getAccessor()); + for (var each : method.getResourcePath()) { + sb.append(each); + } + String methodName = sb.toString(); + + Type[] pathSegmentTypes = method.pathSegmentTypes; + FunctionType innerFn = method.getType(); + List paramTypes = new ArrayList<>(); + boolean hasBTypes = false; + for (Type part : pathSegmentTypes) { + if (part == null) { + paramTypes.add(Builder.anyType()); + } else { + SemType semType = Builder.from(cx, part); + if (!Core.isNever(Core.intersect(semType, Core.B_TYPE_TOP))) { + hasBTypes = true; + paramTypes.add(Core.intersect(semType, Core.SEMTYPE_TOP)); + } else { + paramTypes.add(semType); + } + } + } + for (Parameter paramType : innerFn.getParameters()) { + SemType semType = Builder.from(cx, paramType.type); + if (!Core.isNever(Core.intersect(semType, Core.B_TYPE_TOP))) { + hasBTypes = true; + paramTypes.add(Core.intersect(semType, Core.SEMTYPE_TOP)); + } else { + paramTypes.add(semType); + } + } + SemType rest; + Type restType = innerFn.getRestType(); + if (restType instanceof BArrayType arrayType) { + rest = Builder.from(cx, arrayType.getElementType()); + if (!Core.isNever(Core.intersect(rest, Core.B_TYPE_TOP))) { + hasBTypes = true; + rest = Core.intersect(rest, Core.SEMTYPE_TOP); + } + } else { + rest = Builder.neverType(); + } + + SemType returnType; + if (innerFn.getReturnType() != null) { + returnType = Builder.from(cx, innerFn.getReturnType()); + if (!Core.isNever(Core.intersect(returnType, Core.B_TYPE_TOP))) { + hasBTypes = true; + returnType = Core.intersect(returnType, Core.SEMTYPE_TOP); + } + } else { + returnType = Builder.nilType(); + } + ListDefinition paramListDefinition = new ListDefinition(); + Env env = cx.env; + SemType paramType = paramListDefinition.defineListTypeWrapped(env, paramTypes.toArray(SemType[]::new), + paramTypes.size(), rest, CellAtomicType.CellMutability.CELL_MUT_NONE); + FunctionDefinition fd = new FunctionDefinition(); + SemType semType = fd.define(env, paramType, returnType, innerFn.getQualifiers()); + if (hasBTypes) { + semType = Core.union(semType, BTypeConverter.wrapAsPureBType((BType) innerFn)); + } + return new MethodData(methodName, method.getFlags(), semType); + } + } } 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..67cc1ff7c06e 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,8 @@ 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.Context; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.RefValue; /** @@ -56,4 +58,9 @@ public boolean isNilable() { public boolean isReadOnly() { return true; } + + @Override + SemType createSemType(Context cx) { + 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..3755db46a29e 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,28 +28,42 @@ 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.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +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; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.internal.ValueUtils; import io.ballerina.runtime.internal.scheduling.Scheduler; +import io.ballerina.runtime.internal.types.semtype.MappingDefinition; import io.ballerina.runtime.internal.values.MapValue; import io.ballerina.runtime.internal.values.MapValueImpl; import io.ballerina.runtime.internal.values.ReadOnlyUtils; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; + +import static io.ballerina.runtime.api.types.semtype.Builder.neverType; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_LIMITED; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; /** * {@code BRecordType} represents a user defined record type in Ballerina. * * @since 0.995.0 */ -public class BRecordType extends BStructureType implements RecordType { +public class BRecordType extends BStructureType implements RecordType, PartialSemTypeSupplier, TypeWithShape { private final String internalName; public boolean sealed; public Type restFieldType; @@ -57,6 +71,8 @@ public class BRecordType extends BStructureType implements RecordType { private final boolean readonly; private IntersectionType immutableType; private IntersectionType intersectionType = null; + private MappingDefinition defn; + private final Env env = Env.getInstance(); private final Map> defaultValues = new LinkedHashMap<>(); @@ -218,4 +234,133 @@ public void setDefaultValue(String fieldName, BFunctionPointer defaul return defaultValues; } + @Override + synchronized SemType createSemType(Context cx) { + if (defn != null) { + return defn.getSemType(env); + } + defn = new MappingDefinition(); + Field[] fields = getFields().values().toArray(Field[]::new); + MappingDefinition.Field[] mappingFields = new MappingDefinition.Field[fields.length]; + boolean hasBTypePart = false; + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + boolean isOptional = SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL); + SemType fieldType = Builder.from(cx, field.getFieldType()); + if (!isOptional && Core.isNever(fieldType)) { + return neverType(); + } else if (!Core.isNever(Core.intersect(fieldType, Core.B_TYPE_TOP))) { + hasBTypePart = true; + fieldType = Core.intersect(fieldType, Core.SEMTYPE_TOP); + } + boolean isReadonly = SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY); + if (Core.isNever(fieldType)) { + isReadonly = true; + } + mappingFields[i] = new MappingDefinition.Field(field.getFieldName(), fieldType, + isReadonly, isOptional); + } + CellAtomicType.CellMutability mut = isReadOnly() ? CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + SemType rest = restFieldType != null ? Builder.from(cx, restFieldType) : neverType(); + if (!Core.isNever(Core.intersect(rest, Core.B_TYPE_TOP))) { + hasBTypePart = true; + rest = Core.intersect(rest, Core.SEMTYPE_TOP); + } + if (hasBTypePart) { + cx.markProvisionTypeReset(); + SemType semTypePart = defn.defineMappingTypeWrapped(env, mappingFields, rest, mut); + SemType bTypePart = BTypeConverter.wrapAsPureBType(this); + return Core.union(semTypePart, bTypePart); + } + return defn.defineMappingTypeWrapped(env, mappingFields, rest, mut); + } + + @Override + public void resetSemTypeCache() { + super.resetSemTypeCache(); + defn = null; + } + + @Override + public Optional shapeOf(Context cx, Object object) { + BMap value = (BMap) object; + SemType cachedSemType = value.shapeOf(); + if (cachedSemType != null) { + return Optional.of(cachedSemType); + } + int nFields = value.size(); + List fields = new ArrayList<>(nFields); + Map.Entry[] entries = (Map.Entry[]) value.entrySet().toArray(Map.Entry[]::new); + Set handledFields = new HashSet<>(nFields); + for (int i = 0; i < nFields; i++) { + String fieldName = entries[i].getKey().toString(); + Object fieldValue = entries[i].getValue(); + handledFields.add(fieldName); + boolean readonlyField = fieldIsReadonly(fieldName); + boolean optionalField = fieldIsOptional(fieldName); + Optional fieldType; + if (isReadOnly() || readonlyField) { + optionalField = false; + fieldType = Builder.shapeOf(cx, fieldValue); + } else { + SemType fieldSemType = Builder.from(cx, fieldType(fieldName)); + if (!Core.isNever(Core.intersect(fieldSemType, Core.B_TYPE_TOP))) { + return Optional.empty(); + } + fieldType = Optional.of(fieldSemType); + } + if (fieldType.isEmpty()) { + return Optional.empty(); + } + fields.add(new MappingDefinition.Field(fieldName, fieldType.get(), readonlyField, + optionalField)); + } + for (var field : getFields().values()) { + String name = field.getFieldName(); + if (handledFields.contains(name)) { + continue; + } + boolean isOptional = SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL); + boolean isReadonly = SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY); + SemType fieldType = Builder.from(cx, field.getFieldType()); + if (isReadonly && isOptional && value.get(StringUtils.fromString(name)) == null) { + fieldType = Builder.undef(); + } + if (!Core.isNever(Core.intersect(fieldType, Core.B_TYPE_TOP))) { + return Optional.of(neverType()); + } + fields.add(new MappingDefinition.Field(field.getFieldName(), fieldType, + isReadonly, isOptional)); + } + MappingDefinition md = new MappingDefinition(); + SemType semTypePart; + MappingDefinition.Field[] fieldsArray = fields.toArray(MappingDefinition.Field[]::new); + if (isReadOnly()) { + semTypePart = md.defineMappingTypeWrapped(env, fieldsArray, neverType(), CELL_MUT_NONE); + } else { + SemType rest = restFieldType != null ? Builder.from(cx, restFieldType) : neverType(); + if (!Core.isNever(Core.intersect(rest, Core.B_TYPE_TOP))) { + return Optional.empty(); + } + semTypePart = md.defineMappingTypeWrapped(env, fieldsArray, rest, CELL_MUT_LIMITED); + } + value.cacheShape(semTypePart); + return Optional.of(semTypePart); + } + + private Type fieldType(String fieldName) { + Field field = fields.get(fieldName); + return field == null ? restFieldType : field.getFieldType(); + } + + private boolean fieldIsReadonly(String fieldName) { + Field field = fields.get(fieldName); + return field != null && SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY); + } + + private boolean fieldIsOptional(String fieldName) { + Field field = fields.get(fieldName); + return field != null && SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeSupplier.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeSupplier.java new file mode 100644 index 000000000000..a6695b51c75f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeSupplier.java @@ -0,0 +1,29 @@ +/* + * 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; + +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; + +@FunctionalInterface +public interface BSemTypeSupplier { + + SemType get(Context cx); +} 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..a5e4f61d4199 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,14 @@ 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.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; import io.ballerina.runtime.internal.values.ReadOnlyUtils; import io.ballerina.runtime.internal.values.TupleValueImpl; @@ -33,13 +41,17 @@ import java.util.Optional; import java.util.stream.Collectors; +import static io.ballerina.runtime.api.types.semtype.Builder.neverType; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; + /** * {@code {@link BTupleType}} represents a tuple type in Ballerina. * * @since 0.995.0 */ -public class BTupleType extends BAnnotatableType implements TupleType { +public class BTupleType extends BAnnotatableType implements TupleType, PartialSemTypeSupplier, TypeWithShape { + private static final SemType[] EMPTY_SEMTYPE_ARR = new SemType[0]; private List tupleTypes; private Type restType; private int typeFlags; @@ -50,6 +62,8 @@ public class BTupleType extends BAnnotatableType implements TupleType { private boolean resolving; private boolean resolvingReadonly; private String cachedToString; + private ListDefinition defn; + private final Env env = Env.getInstance(); /** * Create a {@code BTupleType} which represents the tuple type. @@ -165,6 +179,8 @@ public void setMemberTypes(List members, Type restType) { this.restType = restType; } checkAllMembers(); + defn = null; + resetSemTypeCache(); } @Override @@ -297,4 +313,70 @@ public void setIntersectionType(IntersectionType intersectionType) { public String getAnnotationKey() { return Utils.decodeIdentifier(this.typeName); } + + @Override + synchronized SemType createSemType(Context cx) { + if (defn != null) { + return defn.getSemType(env); + } + defn = new ListDefinition(); + SemType[] memberTypes = new SemType[tupleTypes.size()]; + boolean hasBTypePart = false; + for (int i = 0; i < tupleTypes.size(); i++) { + SemType memberType = Builder.from(cx, tupleTypes.get(i)); + if (Core.isNever(memberType)) { + return neverType(); + } else if (!Core.isNever(Core.intersect(memberType, Core.B_TYPE_TOP))) { + hasBTypePart = true; + memberType = Core.intersect(memberType, Core.SEMTYPE_TOP); + } + memberTypes[i] = memberType; + } + CellAtomicType.CellMutability mut = isReadOnly() ? CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + SemType rest = restType != null ? Builder.from(cx, restType) : neverType(); + if (!Core.isNever(Core.intersect(rest, Core.B_TYPE_TOP))) { + hasBTypePart = true; + rest = Core.intersect(rest, Core.SEMTYPE_TOP); + } + if (hasBTypePart) { + cx.markProvisionTypeReset(); + SemType semTypePart = defn.defineListTypeWrapped(env, memberTypes, memberTypes.length, rest, mut); + SemType bTypePart = BTypeConverter.wrapAsPureBType(this); + return Core.union(semTypePart, bTypePart); + } + return defn.defineListTypeWrapped(env, memberTypes, memberTypes.length, rest, mut); + } + + @Override + public void resetSemTypeCache() { + super.resetSemTypeCache(); + defn = null; + } + + @Override + public Optional shapeOf(Context cx, Object object) { + if (!isReadOnly()) { + return Optional.of(get(cx)); + } + BArray value = (BArray) object; + SemType cachedShape = value.shapeOf(); + if (cachedShape != null) { + return Optional.of(cachedShape); + } + int size = value.size(); + SemType[] memberTypes = new SemType[size]; + for (int i = 0; i < size; i++) { + Optional memberType = Builder.shapeOf(cx, value.get(i)); + if (memberType.isEmpty()) { + return Optional.empty(); + } + memberTypes[i] = memberType.get(); + } + ListDefinition ld = new ListDefinition(); + // TODO: cache this in the array value + SemType semType = ld.defineListTypeWrapped(env, memberTypes, memberTypes.length, neverType(), CELL_MUT_NONE); + value.cacheShape(semType); + return Optional.of(semType); + } } 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..840c7014ee59 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,8 +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.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.utils.StringUtils; import io.ballerina.runtime.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.SubTypeData; import java.util.Objects; @@ -37,13 +42,16 @@ * * @since 0.995.0 */ -public abstract class BType implements Type { +public abstract class BType implements Type, SubTypeData, BSemTypeSupplier { + + private static final SemType READONLY_WITH_B_TYPE = Core.union(Builder.readonlyType(), Core.B_TYPE_TOP); protected String typeName; protected Module pkg; protected Class valueClass; private int hashCode; private Type cachedReferredType = null; private Type cachedImpliedType = null; + private volatile SemType cachedSemType = null; protected BType(String typeName, Module pkg, Class valueClass) { this.typeName = typeName; @@ -212,4 +220,33 @@ 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. + // TODO: update this comment to mention what context does + public void resetSemTypeCache() { + cachedSemType = null; + } + + // If any child class partially implement SemType it must override this method. + SemType createSemType(Context cx) { + return BTypeConverter.wrapAsPureBType(this); + } + + @Override + public final SemType get(Context cx) { + SemType semType = cachedSemType; + if (semType == null) { + synchronized (this) { + semType = cachedSemType; + if (semType == null) { + semType = createSemType(cx); + if (isReadOnly()) { + semType = Core.intersect(semType, READONLY_WITH_B_TYPE); + } + cachedSemType = semType; + } + } + } + return semType; + } } 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..0e3640df57b3 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeConverter.java @@ -0,0 +1,204 @@ +/* + * 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.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.Context; +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 implementedTypes = + unionOf(Builder.neverType(), Builder.nilType(), Builder.booleanType(), Builder.intType(), + Builder.floatType(), Builder.decimalType(), Builder.stringType(), Builder.listType(), + Builder.mappingType(), Builder.functionType(), Builder.objectType(), Builder.errorType()); + private static final SemType READONLY_SEMTYPE_PART = Core.intersect(implementedTypes, Builder.readonlyType()); + private static final SemType ANY_SEMTYPE_PART = Core.intersect(implementedTypes, Builder.anyType()); + + 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(Context cx, Type type) { + if (type instanceof SemType semType) { + return semType; + } else if (type instanceof BType bType) { + return fromBType(cx, bType); + } + throw new IllegalArgumentException("Unsupported type: " + type); + } + + private static SemType fromBType(Context cx, BType innerType) { + int staringSize = cx.addProvisionalType(innerType); + SemType res = innerType.get(cx); + cx.emptyProvisionalTypes(staringSize); + return res; + } + + static SemType fromReadonly(BReadonlyType readonlyType) { + SemType bTypePart = wrapAsPureBType(readonlyType); + return Core.union(READONLY_SEMTYPE_PART, bTypePart); + } + + static SemType wrapAsPureBType(BType bType) { + return Builder.basicSubType(BasicTypeCode.BT_B_TYPE, BSubType.wrap(bType)); + } + + static SemType fromAnyType(BAnyType anyType) { + SemType bTypePart = wrapAsPureBType(anyType); + return Core.union(ANY_SEMTYPE_PART, bTypePart); + } + + static SemType fromFiniteType(Context cx, BFiniteType finiteType) { + BTypeParts parts = splitFiniteType(cx, 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(Context cx, BUnionType unionType) { + BTypeParts parts = splitUnion(cx, 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(Context cx, Type type) { + if (type instanceof SemType) { + return new BTypeParts(from(cx, type), Collections.emptyList()); + } else if (type instanceof BUnionType unionType) { + return splitUnion(cx, unionType); + } else if (type instanceof BAnyType anyType) { + return splitAnyType(anyType); + } else if (type instanceof BTypeReferenceType referenceType) { + return split(cx, referenceType.getReferredType()); + } else if (type instanceof BIntersectionType intersectionType) { + return splitIntersection(cx, intersectionType); + } else if (type instanceof BReadonlyType readonlyType) { + return splitReadonly(readonlyType); + } else if (type instanceof BFiniteType finiteType) { + return splitFiniteType(cx, finiteType); + } else if (type instanceof PartialSemTypeSupplier supplier) { + return splitSemTypeSupplier(cx, supplier); + } else { + return new BTypeParts(Builder.neverType(), List.of(type)); + } + } + + private static BTypeParts splitIntersection(Context cx, BIntersectionType intersectionType) { + List members = Collections.unmodifiableList(intersectionType.getConstituentTypes()); + SemType semTypePart = Builder.valType(); + for (Type member : members) { + BTypeParts memberParts = split(cx, member); + semTypePart = Core.intersect(memberParts.semTypePart(), semTypePart); + } + BTypeParts effectiveTypeParts = split(cx, intersectionType.getEffectiveType()); + return new BTypeParts(semTypePart, effectiveTypeParts.bTypeParts()); + } + + private static BTypeParts splitSemTypeSupplier(Context cx, PartialSemTypeSupplier supplier) { + int startingIndex = cx.addProvisionalType((BType) supplier); + SemType semtype = supplier.get(cx); + cx.emptyProvisionalTypes(startingIndex); + SemType bBTypePart = Core.intersect(semtype, Core.B_TYPE_TOP); + if (Core.isNever(bBTypePart)) { + return new BTypeParts(semtype, Collections.emptyList()); + } + SemType pureSemTypePart = Core.intersect(semtype, Core.SEMTYPE_TOP); + BType bType = (BType) Core.subTypeData(semtype, BasicTypeCode.BT_B_TYPE); + return new BTypeParts(pureSemTypePart, List.of(bType)); + } + + private static BTypeParts splitAnyType(BAnyType anyType) { + SemType semTypePart = ANY_SEMTYPE_PART; + if (anyType.isReadOnly()) { + semTypePart = Core.intersect(semTypePart, Builder.readonlyType()); + } + return new BTypeParts(semTypePart, List.of(anyType)); + } + + private static BTypeParts splitFiniteType(Context cx, 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.shapeOf(cx, 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(Context cx, 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(cx, 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..13fed41dec11 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,9 @@ 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.Builder; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; import java.util.Objects; import java.util.Optional; @@ -34,7 +37,7 @@ * * @since 2201.2.0 */ -public class BTypeReferenceType extends BAnnotatableType implements IntersectableReferenceType { +public class BTypeReferenceType extends BAnnotatableType implements IntersectableReferenceType, TypeWithShape { private final int typeFlags; private final boolean readOnly; @@ -126,4 +129,22 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + SemType createSemType(Context cx) { + Type referredType = getReferredType(); + if (referredType instanceof SemType semType) { + return semType; + } + return Builder.from(cx, referredType); + } + + @Override + public Optional shapeOf(Context cx, Object object) { + Type referredType = getReferredType(); + if (referredType instanceof TypeWithShape typeWithShape) { + return typeWithShape.shapeOf(cx, object); + } + return Optional.empty(); + } } 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..3ba87ec4496f 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,8 @@ 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.Context; +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 +169,7 @@ public void setMemberTypes(Type[] members) { } this.memberTypes = readonly ? getReadOnlyTypes(members) : Arrays.asList(members); setFlagsBasedOnMembers(); + resetSemTypeCache(); } public void setOriginalMemberTypes(Type[] originalMemberTypes) { @@ -543,4 +546,9 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + SemType createSemType(Context cx) { + return BTypeConverter.fromUnionType(cx, this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/DistinctIdSupplier.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/DistinctIdSupplier.java new file mode 100644 index 000000000000..3ea468ab9bb3 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/DistinctIdSupplier.java @@ -0,0 +1,75 @@ +/* + * 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; + +import io.ballerina.runtime.api.types.TypeId; +import io.ballerina.runtime.api.types.TypeIdSet; +import io.ballerina.runtime.api.types.semtype.Env; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +final class DistinctIdSupplier implements Supplier> { + + private List ids = null; + private static final Map allocatedIds = new ConcurrentHashMap<>(); + private final Env env; + private final TypeIdSet typeIdSet; + + DistinctIdSupplier(Env env, BTypeIdSet typeIdSet) { + this.env = env; + this.typeIdSet = typeIdSet; + } + + public synchronized Collection get() { + if (ids != null) { + return ids; + } + if (typeIdSet == null) { + return List.of(); + } + ids = typeIdSet.getIds().stream().map(TypeIdWrapper::new).map(typeId -> allocatedIds.computeIfAbsent(typeId, + ignored -> env.distinctAtomCountGetAndIncrement())) + .toList(); + return ids; + } + + // This is to avoid whether id is primary or not affecting the hashcode. + private record TypeIdWrapper(TypeId typeId) { + + @Override + public boolean equals(Object obj) { + if (obj instanceof TypeIdWrapper other) { + return typeId.getName().equals(other.typeId().getName()) && + typeId.getPkg().equals(other.typeId().getPkg()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(typeId.getPkg(), typeId.getName()); + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/PartialSemTypeSupplier.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/PartialSemTypeSupplier.java new file mode 100644 index 000000000000..d517776cbcfd --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/PartialSemTypeSupplier.java @@ -0,0 +1,24 @@ +/* + * 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; + +public interface PartialSemTypeSupplier extends BSemTypeSupplier { + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/TypeWithShape.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/TypeWithShape.java new file mode 100644 index 000000000000..53f867070308 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/TypeWithShape.java @@ -0,0 +1,30 @@ +/* + * 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; + +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.Optional; + +public interface TypeWithShape { + + Optional shapeOf(Context cx, Object object); +} 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..39619b0ee099 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BBooleanSubType.java @@ -0,0 +1,176 @@ +/* + * 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.Context; +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(Context cx) { + return data.isNothing(); + } + + @Override + public SubTypeData data() { + return data.toData(); + } + + // This is instance controlled so only 4 possible instances exists. Default equals is therefore correct + @Override + public int hashCode() { + if (this == ALL) { + return 0; + } + if (this == NOTHING) { + return 1; + } + if (this == TRUE) { + return 2; + } + if (this == FALSE) { + return 3; + } + assert false : "unexpected BBooleanSubType instance"; + return -1; + } + + 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/BCellSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BCellSubType.java new file mode 100644 index 000000000000..951a0d97cb3b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BCellSubType.java @@ -0,0 +1,206 @@ +/* + * 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.Atom; +import io.ballerina.runtime.api.types.semtype.Bdd; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Conjunction; +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.types.semtype.SubType; +import io.ballerina.runtime.api.types.semtype.TypeAtom; + +import java.util.Objects; +import java.util.function.Predicate; + +// TODO: would making this a child class of say BddNode be faster than making this a delegate +// -- Problem with this is modeling type operations (union, intersect, complement) since parent must return a Cell +// as well + +/** + * Runtime representation of CellSubType. + * + * @since 2201.10.0 + */ +public final class BCellSubType extends SubType implements DelegatedSubType { + + public final Bdd inner; + + private BCellSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + public static BCellSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BCellSubType(bdd); + } else if (inner instanceof BCellSubType bCell) { + return new BCellSubType(bCell.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + public static CellAtomicType cellAtomType(Atom atom) { + return (CellAtomicType) ((TypeAtom) atom).atomicType(); + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BCellSubType otherCell)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherCell.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BCellSubType otherCell)) { + throw new IllegalArgumentException("intersect of different subtypes"); + } + return createDelegate(inner.intersect(otherCell.inner)); + } + + @Override + public SubType complement() { + return createDelegate(inner.complement()); + } + + @Override + public boolean isEmpty(Context cx) { + return Bdd.bddEvery(cx, inner, null, null, BCellSubType::cellFormulaIsEmpty); + } + + @Override + public SubType diff(SubType other) { + if (!(other instanceof BCellSubType otherCell)) { + throw new IllegalArgumentException("diff of different subtypes"); + } + + return createDelegate(inner.diff(otherCell.inner)); + } + + @Override + public SubTypeData data() { + throw new IllegalStateException("unimplemented"); + } + + private static boolean cellFormulaIsEmpty(Context cx, Conjunction posList, Conjunction negList) { + CellAtomicType combined; + if (posList == null) { + combined = new CellAtomicType(Builder.valType(), CellAtomicType.CellMutability.CELL_MUT_UNLIMITED); + } else { + combined = cellAtomType(posList.atom()); + Conjunction p = posList.next(); + while (p != null) { + combined = CellAtomicType.intersectCellAtomicType(combined, cellAtomType(p.atom())); + p = p.next(); + } + } + return !cellInhabited(cx, combined, negList); + } + + private static boolean cellInhabited(Context cx, CellAtomicType posCell, Conjunction negList) { + SemType pos = posCell.ty(); + if (Core.isEmpty(cx, pos)) { + return false; + } + return switch (posCell.mut()) { + case CELL_MUT_NONE -> cellMutNoneInhabited(cx, pos, negList); + case CELL_MUT_LIMITED -> cellMutLimitedInhabited(cx, pos, negList); + default -> cellMutUnlimitedInhabited(cx, pos, negList); + }; + } + + private static boolean cellMutUnlimitedInhabited(Context cx, SemType pos, Conjunction negList) { + Conjunction neg = negList; + while (neg != null) { + if (cellAtomType(neg.atom()).mut() == CellAtomicType.CellMutability.CELL_MUT_LIMITED && + Core.isSameType(cx, Builder.valType(), cellAtomType(neg.atom()).ty())) { + return false; + } + neg = neg.next(); + } + SemType negListUnionResult = filteredCellListUnion(negList, + conjunction -> cellAtomType(conjunction.atom()).mut() == + CellAtomicType.CellMutability.CELL_MUT_UNLIMITED); + // We expect `isNever` condition to be `true` when there are no negative atoms with unlimited mutability. + // Otherwise, we do `isEmpty` to conclude on the inhabitance. + return Core.isNever(negListUnionResult) || !Core.isEmpty(cx, Core.diff(pos, negListUnionResult)); + } + + private static boolean cellMutLimitedInhabited(Context cx, SemType pos, Conjunction negList) { + if (negList == null) { + return true; + } + CellAtomicType negAtomicCell = cellAtomType(negList.atom()); + if ((negAtomicCell.mut().compareTo(CellAtomicType.CellMutability.CELL_MUT_LIMITED) >= 0) && + Core.isEmpty(cx, Core.diff(pos, negAtomicCell.ty()))) { + return false; + } + return cellMutLimitedInhabited(cx, pos, negList.next()); + } + + private static boolean cellMutNoneInhabited(Context cx, SemType pos, Conjunction negList) { + SemType negListUnionResult = cellListUnion(negList); + // We expect `isNever` condition to be `true` when there are no negative atoms. + // Otherwise, we do `isEmpty` to conclude on the inhabitance. + return Core.isNever(negListUnionResult) || !Core.isEmpty(cx, Core.diff(pos, negListUnionResult)); + } + + private static SemType cellListUnion(Conjunction negList) { + return filteredCellListUnion(negList, neg -> true); + } + + private static SemType filteredCellListUnion(Conjunction negList, Predicate predicate) { + SemType negUnion = Builder.neverType(); + Conjunction neg = negList; + while (neg != null) { + if (predicate.test(neg)) { + negUnion = Core.union(negUnion, cellAtomType(neg.atom()).ty()); + } + neg = neg.next(); + } + return negUnion; + } + + @Override + public Bdd inner() { + return inner; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BCellSubType other)) { + return false; + } + return Objects.equals(inner, other.inner); + } + + @Override + public int hashCode() { + return Objects.hashCode(inner); + } +} 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..d534ff842e42 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BDecimalSubType.java @@ -0,0 +1,168 @@ +/* + * 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.Context; +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(Context cx) { + 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/BErrorSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BErrorSubType.java new file mode 100644 index 000000000000..b0fe9de0fc28 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BErrorSubType.java @@ -0,0 +1,116 @@ +/* + * 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.Bdd; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.Objects; + +import static io.ballerina.runtime.api.types.semtype.Bdd.bddEveryPositive; + +public class BErrorSubType extends SubType implements DelegatedSubType { + + public final Bdd inner; + + private BErrorSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + public static BErrorSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BErrorSubType(bdd); + } else if (inner.isAll() || inner.isNothing()) { + throw new IllegalStateException("unimplemented"); + } else if (inner instanceof BErrorSubType bError) { + return new BErrorSubType(bError.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BErrorSubType otherError)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherError.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BErrorSubType otherError)) { + throw new IllegalArgumentException("intersect of different subtypes"); + } + return createDelegate(inner.intersect(otherError.inner)); + } + + @Override + public SubType complement() { + return createDelegate(errorSubtypeComplement()); + } + + private SubType errorSubtypeComplement() { + return Builder.bddSubtypeRo().diff(inner); + } + + @Override + public boolean isEmpty(Context cx) { + Bdd b = inner; + // The goal of this is to ensure that mappingFormulaIsEmpty call in errorBddIsEmpty beneath + // does not get an empty posList, because it will interpret that + // as `map` rather than `readonly & map`. + b = b.posMaybeEmpty() ? (Bdd) b.intersect(Builder.bddSubtypeRo()) : b; + return cx.memoSubtypeIsEmpty(cx.mappingMemo, BErrorSubType::errorBddIsEmpty, b); + } + + private static boolean errorBddIsEmpty(Context cx, Bdd b) { + return bddEveryPositive(cx, b, null, null, BMappingSubType::mappingFormulaIsEmpty); + } + + @Override + public SubTypeData data() { + return inner(); + } + + @Override + public Bdd inner() { + return inner; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BErrorSubType that)) { + return false; + } + return Objects.equals(inner, that.inner); + } + + @Override + public int hashCode() { + return Objects.hashCode(inner); + } + +} 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..b2a786ddf450 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BFloatSubType.java @@ -0,0 +1,180 @@ +/* + * 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.Context; +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(Context cx) { + 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/BFunctionSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BFunctionSubType.java new file mode 100644 index 000000000000..be0617fb4fd9 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BFunctionSubType.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.Bdd; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Conjunction; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.FunctionAtomicType; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.Objects; + +import static io.ballerina.runtime.api.types.semtype.Bdd.bddEvery; + +public class BFunctionSubType extends SubType implements DelegatedSubType { + + public final Bdd inner; + + private BFunctionSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + public static BFunctionSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BFunctionSubType(bdd); + } else if (inner.isAll() || inner.isNothing()) { + throw new IllegalStateException("unimplemented"); + } else if (inner instanceof BFunctionSubType bFunction) { + return new BFunctionSubType(bFunction.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BFunctionSubType otherFn)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherFn.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BFunctionSubType otherList)) { + throw new IllegalArgumentException("intersect of different subtypes"); + } + return createDelegate(inner.intersect(otherList.inner)); + } + + @Override + public SubType complement() { + return createDelegate(inner.complement()); + } + + @Override + public boolean isEmpty(Context cx) { + return cx.memoSubtypeIsEmpty(cx.functionMemo, + (context, bdd) -> bddEvery(context, bdd, null, null, BFunctionSubType::functionFormulaIsEmpty), inner); + } + + private static boolean functionFormulaIsEmpty(Context cx, Conjunction pos, Conjunction neg) { + return functionPathIsEmpty(cx, functionUnionParams(cx, pos), functionUnionQualifiers(cx, pos), pos, neg); + } + + private static boolean functionPathIsEmpty(Context cx, SemType params, SemType qualifier, Conjunction pos, + Conjunction neg) { + if (neg == null) { + return false; + } + FunctionAtomicType t = cx.functionAtomicType(neg.atom()); + SemType t0 = t.paramType(); + SemType t1 = t.retType(); + SemType t2 = t.qualifiers(); + return (Core.isSubType(cx, qualifier, t2) && Core.isSubType(cx, t0, params) && + functionPhi(cx, t0, Core.complement(t1), pos)) + || functionPathIsEmpty(cx, params, qualifier, pos, neg.next()); + } + + private static boolean functionPhi(Context cx, SemType t0, SemType t1, Conjunction pos) { + if (pos == null) { + // t0 is NEVER only for function top types with qualifiers + return !Core.isNever(t0) && (Core.isEmpty(cx, t0) || Core.isEmpty(cx, t1)); + } + return functionPhiInner(cx, t0, t1, pos); + } + + private static boolean functionPhiInner(Context cx, SemType t0, SemType t1, Conjunction pos) { + if (pos == null) { + return Core.isEmpty(cx, t0) || Core.isEmpty(cx, t1); + } else { + FunctionAtomicType s = cx.functionAtomicType(pos.atom()); + SemType s0 = s.paramType(); + SemType s1 = s.retType(); + return (Core.isSubType(cx, t0, s0) + || Core.isSubType(cx, functionIntersectRet(cx, pos.next()), Core.complement(t1))) + && functionPhiInner(cx, t0, Core.intersect(t1, s1), pos.next()) + && functionPhiInner(cx, Core.diff(t0, s0), t1, pos.next()); + } + } + + private static SemType functionIntersectRet(Context cx, Conjunction pos) { + if (pos == null) { + return Builder.valType(); + } + return Core.intersect(cx.functionAtomicType(pos.atom()).retType(), functionIntersectRet(cx, pos.next())); + } + + private static SemType functionUnionParams(Context cx, Conjunction pos) { + if (pos == null) { + return Builder.neverType(); + } + return Core.union(cx.functionAtomicType(pos.atom()).paramType(), functionUnionParams(cx, pos.next())); + } + + private static SemType functionUnionQualifiers(Context cx, Conjunction pos) { + if (pos == null) { + return Builder.neverType(); + } + return Core.union(cx.functionAtomicType(pos.atom()).qualifiers(), functionUnionQualifiers(cx, pos.next())); + } + + @Override + public SubTypeData data() { + throw new IllegalStateException("unimplemented"); + } + + @Override + public Bdd inner() { + return inner; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BMappingSubType that)) { + return false; + } + return Objects.equals(inner, that.inner); + } + + @Override + public int hashCode() { + return Objects.hashCode(inner); + } + +} 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..4c0e7d0f46e4 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BIntSubType.java @@ -0,0 +1,325 @@ +/* + * 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.Context; +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(Context cx) { + return data == AllOrNothing.NOTHING; + } + + @Override + public SubTypeData data() { + return data; + } + + public record Range(long min, long max) { + + } + + public static boolean intSubtypeContains(SubTypeData d, long n) { + if (!(d instanceof IntSubTypeData intSubTypeData)) { + return d == AllOrNothing.ALL; + } + return intSubTypeData.contains(n); + } + + static final class IntSubTypeData implements SubTypeData { + + final Range[] ranges; + + private IntSubTypeData(Range range) { + this.ranges = new Range[]{range}; + } + + private IntSubTypeData(Range[] ranges) { + this.ranges = ranges; + } + + public long max() { + return ranges[ranges.length - 1].max; + } + + boolean isRangeOverlap(Range range) { + IntSubTypeData subTypeData = intersect(new IntSubTypeData(range)); + return subTypeData.ranges.length != 0; + } + + private boolean contains(long n) { + for (Range r : ranges) { + if (r.min <= n && n <= r.max) { + return true; + } + } + return false; + } + + 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/BListProj.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BListProj.java new file mode 100644 index 000000000000..961cfab65649 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BListProj.java @@ -0,0 +1,216 @@ +/* + * 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.Atom; +import io.ballerina.runtime.api.types.semtype.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.Bdd; +import io.ballerina.runtime.api.types.semtype.BddAllOrNothing; +import io.ballerina.runtime.api.types.semtype.BddNode; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Conjunction; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.ListAtomicType; +import io.ballerina.runtime.api.types.semtype.Pair; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +import static io.ballerina.runtime.api.types.semtype.Builder.cellContaining; +import static io.ballerina.runtime.api.types.semtype.Builder.roCellContaining; +import static io.ballerina.runtime.api.types.semtype.Conjunction.and; +import static io.ballerina.runtime.api.types.semtype.Core.cellInnerVal; +import static io.ballerina.runtime.api.types.semtype.Core.diff; +import static io.ballerina.runtime.api.types.semtype.Core.getComplexSubtypeData; +import static io.ballerina.runtime.api.types.semtype.Core.isEmpty; +import static io.ballerina.runtime.api.types.semtype.Core.isNever; +import static io.ballerina.runtime.api.types.semtype.Core.isNothingSubtype; +import static io.ballerina.runtime.api.types.semtype.Core.union; +import static io.ballerina.runtime.internal.types.semtype.BIntSubType.intSubtypeContains; +import static io.ballerina.runtime.internal.types.semtype.BListSubType.fixedArrayAnyEmpty; +import static io.ballerina.runtime.internal.types.semtype.BListSubType.listIntersectWith; +import static io.ballerina.runtime.internal.types.semtype.BListSubType.listMemberAtInnerVal; +import static io.ballerina.runtime.internal.types.semtype.BListSubType.listSampleTypes; +import static io.ballerina.runtime.internal.types.semtype.BListSubType.listSamples; + +/** + * utility class for list type projection. + * + * @since 2201.10.0 + */ +public final class BListProj { + + private BListProj() { + } + + public static SemType listProjInnerVal(Context cx, SemType t, SemType k) { + if (t.some == 0) { + return t == Builder.listType() ? Builder.valType() : Builder.neverType(); + } else { + SubTypeData keyData = Core.intSubtype(k); + if (isNothingSubtype(keyData)) { + return Builder.neverType(); + } + return listProjBddInnerVal(cx, keyData, (Bdd) getComplexSubtypeData(t, BasicTypeCode.BT_LIST), null, + null); + } + } + + private static SemType listProjBddInnerVal(Context cx, SubTypeData k, Bdd b, Conjunction pos, Conjunction neg) { + if (b instanceof BddAllOrNothing allOrNothing) { + return allOrNothing.isAll() ? listProjPathInnerVal(cx, k, pos, neg) : Builder.neverType(); + } else { + BddNode bddNode = (BddNode) b; + return union(listProjBddInnerVal(cx, k, bddNode.left(), and(bddNode.atom(), pos), neg), + union(listProjBddInnerVal(cx, k, bddNode.middle(), pos, neg), + listProjBddInnerVal(cx, k, bddNode.right(), pos, and(bddNode.atom(), neg)))); + } + } + + private static SemType listProjPathInnerVal(Context cx, SubTypeData k, Conjunction pos, Conjunction neg) { + FixedLengthArray members; + SemType rest; + if (pos == null) { + members = FixedLengthArray.empty(); + rest = cellContaining(cx.env, union(Builder.valType(), Builder.undef())); + } else { + // combine all the positive tuples using intersection + ListAtomicType lt = cx.listAtomType(pos.atom()); + members = lt.members(); + rest = lt.rest(); + Conjunction p = pos.next(); + // the neg case is in case we grow the array in listInhabited + if (p != null || neg != null) { + members = members.shallowCopy(); + } + + while (true) { + if (p == null) { + break; + } else { + Atom d = p.atom(); + p = p.next(); + lt = cx.listAtomType(d); + Pair + intersected = listIntersectWith(cx.env, members, rest, lt.members(), lt.rest()); + if (intersected == null) { + return Builder.neverType(); + } + members = intersected.first(); + rest = intersected.second(); + } + } + if (fixedArrayAnyEmpty(cx, members)) { + return Builder.neverType(); + } + // Ensure that we can use isNever on rest in listInhabited + if (!isNever(cellInnerVal(rest)) && isEmpty(cx, rest)) { + rest = roCellContaining(cx.env, Builder.neverType()); + } + } + Integer[] indices = listSamples(cx, members, rest, neg); + Pair projSamples = listProjSamples(indices, k); + indices = projSamples.first(); + Pair sampleTypes = listSampleTypes(cx, members, rest, indices); + return listProjExcludeInnerVal(cx, projSamples.first(), + projSamples.second(), + sampleTypes.first(), + sampleTypes.second(), neg); + } + + private static SemType listProjExcludeInnerVal(Context cx, Integer[] indices, Integer[] keyIndices, + SemType[] memberTypes, int nRequired, Conjunction neg) { + SemType p = Builder.neverType(); + if (neg == null) { + int len = memberTypes.length; + for (int k : keyIndices) { + if (k < len) { + p = union(p, cellInnerVal(memberTypes[k])); + } + } + } else { + final ListAtomicType nt = cx.listAtomType(neg.atom()); + if (nRequired > 0 && isNever(listMemberAtInnerVal(nt.members(), nt.rest(), indices[nRequired - 1]))) { + return listProjExcludeInnerVal(cx, indices, keyIndices, memberTypes, nRequired, neg.next()); + } + int negLen = nt.members().fixedLength(); + if (negLen > 0) { + int len = memberTypes.length; + if (len < indices.length && indices[len] < negLen) { + return listProjExcludeInnerVal(cx, indices, keyIndices, memberTypes, nRequired, neg.next()); + } + for (int i = nRequired; i < memberTypes.length; i++) { + if (indices[i] >= negLen) { + break; + } + SemType[] t = Arrays.copyOfRange(memberTypes, 0, i); + p = union(p, listProjExcludeInnerVal(cx, indices, keyIndices, t, nRequired, neg.next())); + } + } + for (int i = 0; i < memberTypes.length; i++) { + SemType d = + diff(cellInnerVal(memberTypes[i]), listMemberAtInnerVal(nt.members(), nt.rest(), indices[i])); + if (!Core.isEmpty(cx, d)) { + SemType[] t = memberTypes.clone(); + t[i] = cellContaining(cx.env, d); + // We need to make index i be required + p = union(p, listProjExcludeInnerVal(cx, indices, keyIndices, t, Integer.max(nRequired, i + 1), + neg.next())); + } + } + } + return p; + } + + private static Pair listProjSamples(Integer[] indices, SubTypeData k) { + List> v = new ArrayList<>(); + for (int i : indices) { + v.add(Pair.from(i, intSubtypeContains(k, i))); + } + if (k instanceof BIntSubType.IntSubTypeData intSubtype) { + for (BIntSubType.Range range : intSubtype.ranges) { + long max = range.max(); + if (range.max() >= 0) { + v.add(Pair.from((int) max, true)); + int min = Integer.max(0, (int) range.min()); + if (min < max) { + v.add(Pair.from(min, true)); + } + } + } + } + v.sort(Comparator.comparingInt(Pair::first)); + List indices1 = new ArrayList<>(); + List keyIndices = new ArrayList<>(); + for (var ib : v) { + if (indices1.isEmpty() || !Objects.equals(ib.first(), indices1.get(indices1.size() - 1))) { + if (ib.second()) { + keyIndices.add(indices1.size()); + } + indices1.add(ib.first()); + } + } + return Pair.from(indices1.toArray(Integer[]::new), keyIndices.toArray(Integer[]::new)); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BListSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BListSubType.java new file mode 100644 index 000000000000..be51941947c3 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BListSubType.java @@ -0,0 +1,458 @@ +/* + * 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.Atom; +import io.ballerina.runtime.api.types.semtype.Bdd; +import io.ballerina.runtime.api.types.semtype.BddAllOrNothing; +import io.ballerina.runtime.api.types.semtype.BddNode; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Conjunction; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.ListAtomicType; +import io.ballerina.runtime.api.types.semtype.Pair; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static io.ballerina.runtime.api.types.semtype.Bdd.bddEvery; +import static io.ballerina.runtime.api.types.semtype.Core.cellContainingInnerVal; +import static io.ballerina.runtime.api.types.semtype.Core.cellInner; +import static io.ballerina.runtime.api.types.semtype.Core.cellInnerVal; +import static io.ballerina.runtime.api.types.semtype.Core.intersectMemberSemTypes; +import static io.ballerina.runtime.internal.types.semtype.BIntSubType.intSubtypeContains; + +// TODO: this has lot of common code with cell (and future mapping), consider refactoring (problem is createDelegate) +public class BListSubType extends SubType implements DelegatedSubType { + + public final Bdd inner; + + private BListSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + public static BListSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BListSubType(bdd); + } else if (inner.isAll() || inner.isNothing()) { + throw new IllegalStateException("unimplemented"); + } else if (inner instanceof BListSubType bList) { + return new BListSubType(bList.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BListSubType otherList)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherList.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BListSubType otherList)) { + throw new IllegalArgumentException("intersect of different subtypes"); + } + return createDelegate(inner.intersect(otherList.inner)); + } + + @Override + public SubType complement() { + return createDelegate(inner.complement()); + } + + @Override + public SubType diff(SubType other) { + if (!(other instanceof BListSubType otherList)) { + throw new IllegalArgumentException("diff of different subtypes"); + } + return createDelegate(inner.diff(otherList.inner)); + } + + @Override + public boolean isEmpty(Context cx) { + return cx.memoSubtypeIsEmpty(cx.listMemo, + (context, bdd) -> bddEvery(context, bdd, null, null, BListSubType::listFormulaIsEmpty), inner); + } + + private static boolean listFormulaIsEmpty(Context cx, Conjunction pos, Conjunction neg) { + FixedLengthArray members; + SemType rest; + if (pos == null) { + ListAtomicType atom = Builder.listAtomicInner(); + members = atom.members(); + rest = atom.rest(); + } else { + // combine all the positive tuples using intersection + ListAtomicType lt = cx.listAtomType(pos.atom()); + members = lt.members(); + rest = lt.rest(); + Conjunction p = pos.next(); + // the neg case is in case we grow the array in listInhabited + if (p != null || neg != null) { + members = members.shallowCopy(); + } + while (true) { + if (p == null) { + break; + } else { + Atom d = p.atom(); + p = p.next(); + lt = cx.listAtomType(d); + Pair + intersected = listIntersectWith(cx.env, members, rest, lt.members(), lt.rest()); + if (intersected == null) { + return true; + } + members = intersected.first(); + rest = intersected.second(); + } + } + if (fixedArrayAnyEmpty(cx, members)) { + return true; + } + } + Integer[] indices = listSamples(cx, members, rest, neg); + Pair sampleTypes = listSampleTypes(cx, members, rest, indices); + return !listInhabited(cx, indices, sampleTypes.first(), sampleTypes.second(), neg); + } + + // This function determines whether a list type P & N is inhabited. + // where P is a positive list type and N is a list of negative list types. + // With just straightforward fixed-length tuples we can consider every index of the tuple. + // But we cannot do this in general because of rest types and fixed length array types e.g. `byte[10000000]`. + // So we consider instead a collection of indices that is sufficient for us to determine inhabitation, + // given the types of P and N. + // `indices` is this list of sample indices: these are indicies into members of the list type. + // We don't represent P directly. Instead P is represented by `memberTypes` and `nRequired`: + // `memberTypes[i]` is the type that P gives to `indices[i]`; + // `nRequired` is the number of members of `memberTypes` that are required by P. + // `neg` represents N. + private static boolean listInhabited(Context cx, Integer[] indices, SemType[] memberTypes, int nRequired, + Conjunction neg) { + if (neg == null) { + return true; + } else { + final ListAtomicType nt = cx.listAtomType(neg.atom()); + if (nRequired > 0 && Core.isNever(listMemberAtInnerVal(nt.members(), nt.rest(), indices[nRequired - 1]))) { + // Skip this negative if it is always shorter than the minimum required by the positive + return listInhabited(cx, indices, memberTypes, nRequired, neg.next()); + } + // Consider cases we can avoid this negative by having a sufficiently short list + int negLen = nt.members().fixedLength(); + if (negLen > 0) { + int len = memberTypes.length; + // If we have isEmpty(T1 & S1) or isEmpty(T2 & S2) then we have [T1, T2] / [S1, S2] = [T1, T2]. + // Therefore, we can skip the negative + for (int i = 0; i < len; i++) { + int index = indices[i]; + if (index >= negLen) { + break; + } + SemType negMemberType = listMemberAt(nt.members(), nt.rest(), index); + SemType common = Core.intersect(memberTypes[i], negMemberType); + if (Core.isEmpty(cx, common)) { + return listInhabited(cx, indices, memberTypes, nRequired, neg.next()); + } + } + // Consider cases we can avoid this negative by having a sufficiently short list + if (len < indices.length && indices[len] < negLen) { + return listInhabited(cx, indices, memberTypes, nRequired, neg.next()); + } + for (int i = nRequired; i < memberTypes.length; i++) { + if (indices[i] >= negLen) { + break; + } + // TODO: avoid creating new arrays here, maybe use an object pool for this + // -- Or use a copy on write array? + SemType[] t = Arrays.copyOfRange(memberTypes, 0, i); + if (listInhabited(cx, indices, t, nRequired, neg.next())) { + return true; + } + } + } + // Now we need to explore the possibility of shapes with length >= neglen + // This is the heart of the algorithm. + // For [v0, v1] not to be in [t0,t1], there are two possibilities + // (1) v0 is not in t0, or + // (2) v1 is not in t1 + // Case (1) + // For v0 to be in s0 but not t0, d0 must not be empty. + // We must then find a [v0,v1] satisfying the remaining negated tuples, + // such that v0 is in d0. + // SemType d0 = diff(s[0], t[0]); + // if !isEmpty(cx, d0) && tupleInhabited(cx, [d0, s[1]], neg.rest) { + // return true; + // } + // Case (2) + // For v1 to be in s1 but not t1, d1 must not be empty. + // We must then find a [v0,v1] satisfying the remaining negated tuples, + // such that v1 is in d1. + // SemType d1 = diff(s[1], t[1]); + // return !isEmpty(cx, d1) && tupleInhabited(cx, [s[0], d1], neg.rest); + // We can generalize this to tuples of arbitrary length. + for (int i = 0; i < memberTypes.length; i++) { + SemType d = Core.diff(memberTypes[i], listMemberAt(nt.members(), nt.rest(), indices[i])); + if (!Core.isEmpty(cx, d)) { + SemType[] t = memberTypes.clone(); + t[i] = d; + // We need to make index i be required + if (listInhabited(cx, indices, t, Integer.max(nRequired, i + 1), neg.next())) { + return true; + } + } + } + // This is correct for length 0, because we know that the length of the + // negative is 0, and [] - [] is empty. + return false; + } + } + + public static Pair listSampleTypes(Context cx, FixedLengthArray members, + SemType rest, Integer[] indices) { + List memberTypes = new ArrayList<>(indices.length); + int nRequired = 0; + for (int i = 0; i < indices.length; i++) { + int index = indices[i]; + SemType t = cellContainingInnerVal(cx.env, listMemberAt(members, rest, index)); + if (Core.isEmpty(cx, t)) { + break; + } + memberTypes.add(t); + if (index < members.fixedLength()) { + nRequired = i + 1; + } + } + SemType[] buffer = new SemType[memberTypes.size()]; + return Pair.from(memberTypes.toArray(buffer), nRequired); + } + + // Return a list of sample indices for use as second argument of `listInhabited`. + // The positive list type P is represented by `members` and `rest`. + // The negative list types N are represented by `neg` + // The `indices` list (first member of return value) is constructed in two stages. + // First, the set of all non-negative integers is partitioned so that two integers are + // in different partitions if they get different types as an index in P or N. + // Second, we choose a number of samples from each partition. It doesn't matter + // which sample we choose, but (this is the key point) we need at least as many samples + // as there are negatives in N, so that for each negative we can freely choose a type for the sample + // to avoid being matched by that negative. + public static Integer[] listSamples(Context cx, FixedLengthArray members, SemType rest, Conjunction neg) { + int maxInitialLength = members.initial().length; + List fixedLengths = new ArrayList<>(); + fixedLengths.add(members.fixedLength()); + Conjunction tem = neg; + int nNeg = 0; + while (true) { + if (tem != null) { + ListAtomicType lt = cx.listAtomType(tem.atom()); + FixedLengthArray m = lt.members(); + maxInitialLength = Integer.max(maxInitialLength, m.initial().length); + if (m.fixedLength() > maxInitialLength) { + fixedLengths.add(m.fixedLength()); + } + nNeg += 1; + tem = tem.next(); + } else { + break; + } + } + Collections.sort(fixedLengths); + // `boundaries` partitions the non-negative integers + // Construct `boundaries` from `fixedLengths` and `maxInitialLength` + // An index b is a boundary point if indices < b are different from indices >= b + //int[] boundaries = from int i in 1 ... maxInitialLength select i; + List boundaries = new ArrayList<>(fixedLengths.size()); + for (int i = 1; i <= maxInitialLength; i++) { + boundaries.add(i); + } + for (int n : fixedLengths) { + // this also removes duplicates + if (boundaries.isEmpty() || n > boundaries.get(boundaries.size() - 1)) { + boundaries.add(n); + } + } + // Now construct the list of indices by taking nNeg samples from each partition. + List indices = new ArrayList<>(boundaries.size()); + int lastBoundary = 0; + if (nNeg == 0) { + // this is needed for when this is used in listProj + nNeg = 1; + } + for (int b : boundaries) { + int segmentLength = b - lastBoundary; + // Cannot have more samples than are in the parition. + int nSamples = Integer.min(segmentLength, nNeg); + for (int i = b - nSamples; i < b; i++) { + indices.add(i); + } + lastBoundary = b; + } + for (int i = 0; i < nNeg; i++) { + // Be careful to avoid integer overflow. + if (lastBoundary > Integer.MAX_VALUE - i) { + break; + } + indices.add(lastBoundary + i); + } + Integer[] arr = new Integer[indices.size()]; + return indices.toArray(arr); + } + + public static boolean fixedArrayAnyEmpty(Context cx, FixedLengthArray array) { + for (var t : array.initial()) { + if (Core.isEmpty(cx, t)) { + return true; + } + } + return false; + } + + public static Pair listIntersectWith(Env env, FixedLengthArray members1, SemType rest1, + FixedLengthArray members2, SemType rest2) { + + if (listLengthsDisjoint(members1, rest1, members2, rest2)) { + return null; + } + int max = Integer.max(members1.fixedLength(), members2.fixedLength()); + SemType[] initial = new SemType[max]; + for (int i = 0; i < max; i++) { + initial[i] = + intersectMemberSemTypes(env, listMemberAt(members1, rest1, i), listMemberAt(members2, rest2, i)); + } + return Pair.from(FixedLengthArray.from(initial, + Integer.max(members1.fixedLength(), members2.fixedLength())), + intersectMemberSemTypes(env, rest1, rest2)); + } + + private static boolean listLengthsDisjoint(FixedLengthArray members1, SemType rest1, + FixedLengthArray members2, SemType rest2) { + int len1 = members1.fixedLength(); + int len2 = members2.fixedLength(); + if (len1 < len2) { + return Core.isNever(cellInnerVal(rest1)); + } + if (len2 < len1) { + return Core.isNever(cellInnerVal(rest2)); + } + return false; + } + + private static SemType listMemberAt(FixedLengthArray fixedArray, SemType rest, int index) { + if (index < fixedArray.fixedLength()) { + return fixedArrayGet(fixedArray, index); + } + return rest; + } + + private static SemType fixedArrayGet(FixedLengthArray members, int index) { + int memberLen = members.initial().length; + int i = Integer.min(index, memberLen - 1); + return members.initial()[i]; + } + + public static SemType listMemberAtInnerVal(FixedLengthArray fixedArray, SemType rest, int index) { + return cellInnerVal(listMemberAt(fixedArray, rest, index)); + } + + public static SemType bddListMemberTypeInnerVal(Context cx, Bdd b, SubTypeData key, SemType accum) { + if (b instanceof BddAllOrNothing allOrNothing) { + return allOrNothing.isAll() ? accum : Builder.neverType(); + } else { + BddNode bddNode = (BddNode) b; + return Core.union(bddListMemberTypeInnerVal(cx, bddNode.left(), key, + Core.intersect(listAtomicMemberTypeInnerVal(cx.listAtomType(bddNode.atom()), key), accum)), + Core.union(bddListMemberTypeInnerVal(cx, bddNode.middle(), key, accum), + bddListMemberTypeInnerVal(cx, bddNode.right(), key, accum))); + } + } + + private static SemType listAtomicMemberTypeInnerVal(ListAtomicType atomic, SubTypeData key) { + return Core.diff(listAtomicMemberTypeInner(atomic, key), Builder.undef()); + } + + private static SemType listAtomicMemberTypeInner(ListAtomicType atomic, SubTypeData key) { + return listAtomicMemberTypeAtInner(atomic.members(), atomic.rest(), key); + } + + static SemType listAtomicMemberTypeAtInner(FixedLengthArray fixedArray, SemType rest, SubTypeData key) { + if (key instanceof BIntSubType.IntSubTypeData intSubtype) { + SemType m = Builder.neverType(); + int initLen = fixedArray.initial().length; + int fixedLen = fixedArray.fixedLength(); + if (fixedLen != 0) { + for (int i = 0; i < initLen; i++) { + if (intSubtypeContains(key, i)) { + m = Core.union(m, cellInner(fixedArrayGet(fixedArray, i))); + } + } + if (intSubtype.isRangeOverlap(new BIntSubType.Range(initLen, fixedLen - 1))) { + m = Core.union(m, cellInner(fixedArrayGet(fixedArray, fixedLen - 1))); + } + } + if (fixedLen == 0 || intSubtype.max() > fixedLen - 1) { + m = Core.union(m, cellInner(rest)); + } + return m; + } + SemType m = cellInner(rest); + if (fixedArray.fixedLength() > 0) { + for (SemType ty : fixedArray.initial()) { + m = Core.union(m, cellInner(ty)); + } + } + return m; + } + + @Override + public SubTypeData data() { + throw new IllegalStateException("unimplemented"); + } + + @Override + public Bdd inner() { + return inner; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BListSubType that)) { + return false; + } + return Objects.equals(inner, that.inner); + } + + @Override + public int hashCode() { + return Objects.hashCode(inner); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BMappingProj.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BMappingProj.java new file mode 100644 index 000000000000..39ca3732dd58 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BMappingProj.java @@ -0,0 +1,118 @@ +/* + * 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.Bdd; +import io.ballerina.runtime.api.types.semtype.BddAllOrNothing; +import io.ballerina.runtime.api.types.semtype.BddNode; +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.MappingAtomicType; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.ArrayList; +import java.util.List; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_MAPPING; +import static io.ballerina.runtime.api.types.semtype.Core.diff; +import static io.ballerina.runtime.api.types.semtype.Core.getComplexSubtypeData; +import static io.ballerina.runtime.api.types.semtype.Core.isNothingSubtype; +import static io.ballerina.runtime.api.types.semtype.Core.stringSubtype; + +public final class BMappingProj { + + private BMappingProj() { + } + + public static SemType mappingMemberTypeInnerVal(Context cx, SemType t, SemType k) { + return diff(mappingMemberTypeInner(cx, t, k), Builder.undef()); + } + + // This computes the spec operation called "member type of K in T", + // for when T is a subtype of mapping, and K is either `string` or a singleton string. + // This is what Castagna calls projection. + static SemType mappingMemberTypeInner(Context cx, SemType t, SemType k) { + if (t.some() == 0) { + return (t.all() & Builder.mappingType().all()) != 0 ? Builder.valType() : Builder.undef(); + } else { + SubTypeData keyData = stringSubtype(k); + if (isNothingSubtype(keyData)) { + return Builder.undef(); + } + return bddMappingMemberTypeInner(cx, (Bdd) getComplexSubtypeData(t, BT_MAPPING), keyData, + Builder.inner()); + } + } + + static SemType bddMappingMemberTypeInner(Context cx, Bdd b, SubTypeData key, SemType accum) { + if (b instanceof BddAllOrNothing allOrNothing) { + return allOrNothing.isAll() ? accum : Builder.neverType(); + } else { + BddNode bdd = (BddNode) b; + return Core.union( + bddMappingMemberTypeInner(cx, bdd.left(), key, + Core.intersect(mappingAtomicMemberTypeInner(cx.mappingAtomType(bdd.atom()), key), + accum)), + Core.union(bddMappingMemberTypeInner(cx, bdd.middle(), key, accum), + bddMappingMemberTypeInner(cx, bdd.right(), key, accum))); + } + } + + static SemType mappingAtomicMemberTypeInner(MappingAtomicType atomic, SubTypeData key) { + SemType memberType = null; + for (SemType ty : mappingAtomicApplicableMemberTypesInner(atomic, key)) { + if (memberType == null) { + memberType = ty; + } else { + memberType = Core.union(memberType, ty); + } + } + return memberType == null ? Builder.undef() : memberType; + } + + static List mappingAtomicApplicableMemberTypesInner(MappingAtomicType atomic, SubTypeData key) { + List types = new ArrayList<>(atomic.types().length); + for (SemType t : atomic.types()) { + types.add(Core.cellInner(t)); + } + + List memberTypes = new ArrayList<>(); + SemType rest = Core.cellInner(atomic.rest()); + if (isAllSubtype(key)) { + memberTypes.addAll(types); + memberTypes.add(rest); + } else { + BStringSubType.StringSubtypeListCoverage coverage = + ((BStringSubType.StringSubTypeData) key).stringSubtypeListCoverage(atomic.names()); + for (int index : coverage.indices()) { + memberTypes.add(types.get(index)); + } + if (!coverage.isSubType()) { + memberTypes.add(rest); + } + } + return memberTypes; + } + + static boolean isAllSubtype(SubTypeData d) { + return d == AllOrNothing.ALL; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BMappingSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BMappingSubType.java new file mode 100644 index 000000000000..da267c1ef694 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BMappingSubType.java @@ -0,0 +1,208 @@ +/* + * 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.Bdd; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Conjunction; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.FieldPair; +import io.ballerina.runtime.api.types.semtype.FieldPairs; +import io.ballerina.runtime.api.types.semtype.MappingAtomicType; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.Arrays; +import java.util.Objects; + +import static io.ballerina.runtime.api.types.semtype.Bdd.bddEvery; + +public class BMappingSubType extends SubType implements DelegatedSubType { + + public final Bdd inner; + + private BMappingSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + public static BMappingSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BMappingSubType(bdd); + } else if (inner.isAll() || inner.isNothing()) { + throw new IllegalStateException("unimplemented"); + } else if (inner instanceof BMappingSubType bMapping) { + return new BMappingSubType(bMapping.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public Bdd inner() { + return inner; + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BMappingSubType otherList)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherList.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BMappingSubType otherList)) { + throw new IllegalArgumentException("intersect of different subtypes"); + } + return createDelegate(inner.intersect(otherList.inner)); + } + + @Override + public SubType complement() { + return createDelegate(inner.complement()); + } + + @Override + public boolean isEmpty(Context cx) { + return cx.memoSubtypeIsEmpty(cx.mappingMemo, + (context, bdd) -> bddEvery(context, bdd, null, null, BMappingSubType::mappingFormulaIsEmpty), inner); + } + + static boolean mappingFormulaIsEmpty(Context cx, Conjunction posList, Conjunction negList) { + MappingAtomicType combined; + if (posList == null) { + combined = Builder.mappingAtomicInner(); + } else { + // combine all the positive atoms using intersection + combined = cx.mappingAtomType(posList.atom()); + Conjunction p = posList.next(); + while (true) { + if (p == null) { + break; + } else { + MappingAtomicType m = + combined.intersectMapping(cx.env, cx.mappingAtomType(p.atom())); + if (m == null) { + return true; + } else { + combined = m; + } + p = p.next(); + } + } + for (SemType t : combined.types()) { + if (Core.isEmpty(cx, t)) { + return true; + } + } + + } + return !mappingInhabited(cx, combined, negList); + } + + private static boolean mappingInhabited(Context cx, MappingAtomicType pos, Conjunction negList) { + if (negList == null) { + return true; + } else { + MappingAtomicType neg = cx.mappingAtomType(negList.atom()); + + if (!Core.isEmpty(cx, Core.diff(pos.rest(), neg.rest()))) { + return mappingInhabited(cx, pos, negList.next()); + } + for (FieldPair fieldPair : new FieldPairs(pos, neg)) { + SemType d = Core.diff(fieldPair.type1(), fieldPair.type2()); + if (!Core.isEmpty(cx, d)) { + MappingAtomicType mt; + if (fieldPair.index1() == null) { + // the posType came from the rest type + mt = insertField(pos, fieldPair.name(), d); + } else { + SemType[] posTypes = pos.types().clone(); + posTypes[fieldPair.index1()] = d; + mt = new MappingAtomicType(pos.names(), posTypes, pos.rest()); + } + if (mappingInhabited(cx, mt, negList.next())) { + return true; + } + } + } + return false; + } + } + + private static MappingAtomicType insertField(MappingAtomicType m, String name, SemType t) { + String[] orgNames = m.names(); + String[] names = shallowCopyStrings(orgNames, orgNames.length + 1); + SemType[] orgTypes = m.types(); + SemType[] types = shallowCopySemTypes(orgTypes, orgTypes.length + 1); + int i = orgNames.length; + while (true) { + if (i == 0 || Common.codePointCompare(names[i - 1], name)) { + names[i] = name; + types[i] = t; + break; + } + names[i] = names[i - 1]; + types[i] = types[i - 1]; + i -= 1; + } + return new MappingAtomicType(names, types, m.rest()); + } + + static SemType[] shallowCopySemTypes(SemType[] v, int newLength) { + return Arrays.copyOf(v, newLength); + } + + private static String[] shallowCopyStrings(String[] v, int newLength) { + return Arrays.copyOf(v, newLength); + } + + @Override + public SubTypeData data() { + return inner(); + } + + @Override + public SubType diff(SubType other) { + if (!(other instanceof BMappingSubType otherList)) { + throw new IllegalArgumentException("diff of different subtypes"); + } + return createDelegate(inner.diff(otherList.inner)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BMappingSubType that)) { + return false; + } + return Objects.equals(inner, that.inner); + } + + @Override + public int hashCode() { + return Objects.hashCode(inner); + } + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BObjectSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BObjectSubType.java new file mode 100644 index 000000000000..49db4dd05f07 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BObjectSubType.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.Bdd; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.Objects; + +import static io.ballerina.runtime.api.types.semtype.Bdd.bddEveryPositive; + +public final class BObjectSubType extends SubType implements DelegatedSubType { + + public final Bdd inner; + + private BObjectSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BObjectSubType otherObject)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherObject.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BObjectSubType otherObject)) { + throw new IllegalArgumentException("intersect of different subtypes"); + } + return createDelegate(inner.intersect(otherObject.inner)); + } + + @Override + public SubType complement() { + return createDelegate(inner.complement()); + } + + @Override + public boolean isEmpty(Context cx) { + return cx.memoSubtypeIsEmpty(cx.mappingMemo, + (context, bdd) -> bddEveryPositive(context, bdd, null, null, BMappingSubType::mappingFormulaIsEmpty), + inner); + } + + @Override + public SubTypeData data() { + throw new UnsupportedOperationException("Method not implemented"); + } + + public static BObjectSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BObjectSubType(bdd); + } else if (inner.isAll() || inner.isNothing()) { + throw new IllegalStateException("unimplemented"); + } else if (inner instanceof BObjectSubType bMapping) { + return new BObjectSubType(bMapping.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public Bdd inner() { + throw new UnsupportedOperationException("Method not implemented"); + } + + @Override + public SubType diff(SubType other) { + if (!(other instanceof BObjectSubType otherObject)) { + throw new IllegalArgumentException("diff of different subtypes"); + } + return createDelegate(inner.diff(otherObject.inner)); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof BObjectSubType that)) { + return false; + } + return Objects.equals(inner, that.inner); + } + + @Override + public int hashCode() { + return Objects.hashCode(inner); + } + +} 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..493ba9f3fd91 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BStringSubType.java @@ -0,0 +1,263 @@ +/* + * 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.Context; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * 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(Context cx) { + return data == AllOrNothing.NOTHING; + } + + @Override + public SubTypeData data() { + return data; + } + + static void stringListIntersect(String[] values, String[] target, List indices) { + int i1 = 0; + int i2 = 0; + int len1 = values.length; + int len2 = target.length; + while (true) { + if (i1 >= len1 || i2 >= len2) { + break; + } else { + switch (compareStrings(values[i1], target[i2])) { + case EQ: + indices.add(i1); + i1 += 1; + i2 += 1; + break; + case LT: + i1 += 1; + break; + case GT: + i2 += 1; + break; + default: + throw new AssertionError("Invalid comparison value!"); + } + } + } + } + + private static ComparisonResult compareStrings(String s1, String s2) { + return Objects.equals(s1, s2) ? ComparisonResult.EQ : + (Common.codePointCompare(s1, s2) ? ComparisonResult.LT : ComparisonResult.GT); + } + + private enum ComparisonResult { + EQ, + LT, + GT + } + + record StringSubTypeData(ValueData chars, ValueData nonChars) implements SubTypeData { + + StringSubtypeListCoverage stringSubtypeListCoverage(String[] values) { + List indices = new ArrayList<>(); + ValueData ch = chars(); + ValueData nonChar = nonChars(); + int stringConsts = 0; + if (ch.allowed) { + stringListIntersect(values, ch.values, indices); + stringConsts = ch.values.length; + } else if (ch.values.length == 0) { + for (int i = 0; i < values.length; i++) { + if (values[i].length() == 1) { + indices.add(i); + } + } + } + if (nonChar.allowed) { + stringListIntersect(values, nonChar.values, indices); + stringConsts += nonChar.values.length; + } else if (nonChar.values.length == 0) { + for (int i = 0; i < values.length; i++) { + if (values[i].length() != 1) { + indices.add(i); + } + } + } + int[] inds = indices.stream().mapToInt(i -> i).toArray(); + return new StringSubtypeListCoverage(stringConsts == indices.size(), inds); + } + } + + record StringSubtypeListCoverage(boolean isSubType, int[] indices) { + + } + + 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..f67f47b8a2f0 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BSubType.java @@ -0,0 +1,74 @@ +/* + * 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.Context; +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(Context cx) { + 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/Common.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Common.java new file mode 100644 index 000000000000..d4dda91cf99d --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Common.java @@ -0,0 +1,49 @@ +/* + * 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; + +public final class Common { + + private Common() { + } + + public static boolean codePointCompare(String s1, String s2) { + if (s1.equals(s2)) { + return false; + } + int len1 = s1.length(); + int len2 = s2.length(); + if (len1 < len2 && s2.substring(0, len1).equals(s1)) { + return true; + } + int cpCount1 = s1.codePointCount(0, len1); + int cpCount2 = s2.codePointCount(0, len2); + for (int cp = 0; cp < cpCount1 && cp < cpCount2; ) { + int codepoint1 = s1.codePointAt(cp); + int codepoint2 = s2.codePointAt(cp); + if (codepoint1 == codepoint2) { + cp++; + continue; + } + return codepoint1 < codepoint2; + } + return false; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Definition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Definition.java new file mode 100644 index 000000000000..0e144cc1fab9 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Definition.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.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +// NOTE: definitions are not thread safe +public interface Definition { + + SemType getSemType(Env env); +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DelegatedSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DelegatedSubType.java new file mode 100644 index 000000000000..3a145f5f44c0 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DelegatedSubType.java @@ -0,0 +1,26 @@ +/* + * 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.Bdd; + +public interface DelegatedSubType { + + Bdd inner(); +} 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..1b01d52aa5a1 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/EnumerableSubtypeData.java @@ -0,0 +1,180 @@ +/* + * + * 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.Arrays; +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(); + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof EnumerableSubtypeData other)) { + return false; + } + return other.allowed() == this.allowed() && Arrays.equals(other.values(), this.values()); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + 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/ErrorUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ErrorUtils.java new file mode 100644 index 000000000000..f54380d95459 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ErrorUtils.java @@ -0,0 +1,60 @@ +/* + * 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.Bdd; +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.api.types.semtype.SubType; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_ERROR; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_MAPPING; +import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; +import static io.ballerina.runtime.api.types.semtype.Builder.basicSubType; +import static io.ballerina.runtime.api.types.semtype.RecAtom.createDistinctRecAtom; + +public final class ErrorUtils { + + private ErrorUtils() { + } + + public static SemType errorDetail(SemType detail) { + SubTypeData data = Core.subTypeData(detail, BT_MAPPING); + if (data == AllOrNothing.ALL) { + return Builder.errorType(); + } else if (data == AllOrNothing.NOTHING) { + return Builder.neverType(); + } + + assert data instanceof Bdd; + SubType sd = ((Bdd) data).intersect(Builder.bddSubtypeRo()); + if (sd.equals(Builder.bddSubtypeRo())) { + return Builder.errorType(); + } + return basicSubType(BT_ERROR, BErrorSubType.createDelegate(sd)); + } + + public static SemType errorDistinct(int distinctId) { + assert distinctId >= 0; + Bdd bdd = bddAtom(createDistinctRecAtom(-distinctId - 1)); + return basicSubType(BT_ERROR, BErrorSubType.createDelegate(bdd)); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FixedLengthArray.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FixedLengthArray.java new file mode 100644 index 000000000000..9d08326ffa82 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FixedLengthArray.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.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.Arrays; +import java.util.Objects; + +public final class FixedLengthArray { + + private final SemType[] initial; + private final int fixedLength; + private Integer hashCode; + + private FixedLengthArray(SemType[] initial, int fixedLength) { + for (SemType semType : initial) { + if (semType == null) { + throw new IllegalArgumentException("initial members can't be null"); + } + } + this.initial = initial; + this.fixedLength = fixedLength; + } + + static FixedLengthArray from(SemType[] initial, int fixedLength) { + return new FixedLengthArray(initial, fixedLength); + } + + private static final FixedLengthArray EMPTY = new FixedLengthArray(new SemType[0], 0); + + static FixedLengthArray normalized(SemType[] initial, int fixedLength) { + if (initial.length < 2) { + return new FixedLengthArray(initial, fixedLength); + } + int i = initial.length - 1; + SemType last = initial[i]; + i -= 1; + while (i >= 0) { + if (last != initial[i]) { + break; + } + i -= 1; + } + int length = Integer.max(1, i + 2); + SemType[] buffer = new SemType[length]; + if (length == 1) { + buffer[0] = last; + } else { + System.arraycopy(initial, 0, buffer, 0, length); + } + return new FixedLengthArray(buffer, fixedLength); + } + + public static FixedLengthArray empty() { + return EMPTY; + } + + public FixedLengthArray shallowCopy() { + // TODO: may be create a copy of write array instead + return new FixedLengthArray(initial.clone(), fixedLength); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FixedLengthArray that)) { + return false; + } + return fixedLength == that.fixedLength && Objects.deepEquals(initial, that.initial); + } + + @Override + public int hashCode() { + Integer result = hashCode; + if (result == null) { + synchronized (this) { + result = hashCode; + if (result == null) { + hashCode = result = computeHashCode(); + } + } + } + return result; + } + + private int computeHashCode() { + return Objects.hash(Arrays.hashCode(initial), fixedLength); + } + + public int fixedLength() { + return fixedLength; + } + + public SemType[] initial() { + return initial; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionDefinition.java new file mode 100644 index 000000000000..381e6fde53a0 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionDefinition.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.Atom; +import io.ballerina.runtime.api.types.semtype.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.BddNode; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.FunctionAtomicType; +import io.ballerina.runtime.api.types.semtype.RecAtom; +import io.ballerina.runtime.api.types.semtype.SemType; + +public class FunctionDefinition implements Definition { + + private RecAtom rec; + private SemType semType; + + @Override + public SemType getSemType(Env env) { + if (this.semType != null) { + return this.semType; + } else { + RecAtom rec = env.recFunctionAtom(); + this.rec = rec; + return this.createSemType(rec); + } + } + + private SemType createSemType(Atom atom) { + BddNode bdd = BddNode.bddAtom(atom); + SemType semType = Builder.basicSubType(BasicTypeCode.BT_FUNCTION, BFunctionSubType.createDelegate(bdd)); + this.semType = semType; + return semType; + } + + public SemType define(Env env, SemType args, SemType ret, FunctionQualifiers qualifiers) { + FunctionAtomicType atomicType = new FunctionAtomicType(args, ret, qualifiers.toSemType(env)); + RecAtom rec = this.rec; + Atom atom; + if (rec != null) { + atom = rec; + env.setRecFunctionAtomType(rec, atomicType); + } else { + atom = env.functionAtom(atomicType); + } + return this.createSemType(atom); + } + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionQualifiers.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionQualifiers.java new file mode 100644 index 000000000000..850b6131466d --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionQualifiers.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +public final class FunctionQualifiers { + + private static final FunctionQualifiers DEFAULT = new FunctionQualifiers(false, false); + private final boolean isolated; + private final boolean transactional; + private SemType semType; + + private FunctionQualifiers(boolean isolated, boolean transactional) { + this.isolated = isolated; + this.transactional = transactional; + } + + public static FunctionQualifiers create(boolean isolated, boolean transactional) { + if (!isolated && !transactional) { + return DEFAULT; + } + return new FunctionQualifiers(isolated, transactional); + } + + synchronized SemType toSemType(Env env) { + if (semType == null) { + ListDefinition ld = new ListDefinition(); + SemType[] members = { + isolated ? Builder.booleanConst(true) : Builder.booleanType(), + transactional ? Builder.booleanType() : Builder.booleanConst(false) + }; + semType = ld.defineListTypeWrapped(env, members, 2, Builder.neverType(), + CellAtomicType.CellMutability.CELL_MUT_NONE); + } + return semType; + } + + public boolean isolated() { + return isolated; + } + + public boolean transactional() { + return transactional; + } + + @Override + public String toString() { + return "FunctionQualifiers[" + + "isolated=" + isolated + ", " + + "transactional=" + transactional + ']'; + } + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListDefinition.java new file mode 100644 index 000000000000..c420dff26dfc --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListDefinition.java @@ -0,0 +1,84 @@ +/* + * 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.Atom; +import io.ballerina.runtime.api.types.semtype.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.BddNode; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.ListAtomicType; +import io.ballerina.runtime.api.types.semtype.RecAtom; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; +import static io.ballerina.runtime.api.types.semtype.Builder.basicSubType; +import static io.ballerina.runtime.api.types.semtype.Builder.cellContaining; +import static io.ballerina.runtime.api.types.semtype.Builder.undef; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; +import static io.ballerina.runtime.api.types.semtype.Core.isNever; +import static io.ballerina.runtime.api.types.semtype.Core.union; + +public class ListDefinition implements Definition { + + private RecAtom rec = null; + private SemType semType = null; + + @Override + public SemType getSemType(Env env) { + SemType s = this.semType; + if (s == null) { + RecAtom rec = env.recListAtom(); + this.rec = rec; + return this.createSemType(env, rec); + } + return s; + } + + public SemType defineListTypeWrapped(Env env, SemType[] initial, int fixedLength, SemType rest, + CellAtomicType.CellMutability mut) { + SemType[] initialCells = new SemType[initial.length]; + for (int i = 0; i < initial.length; i++) { + initialCells[i] = cellContaining(env, initial[i], mut); + } + SemType restCell = cellContaining(env, union(rest, undef()), isNever(rest) ? CELL_MUT_NONE : mut); + return define(env, initialCells, fixedLength, restCell); + } + + private SemType define(Env env, SemType[] initial, int fixedLength, SemType rest) { + FixedLengthArray members = FixedLengthArray.normalized(initial, fixedLength); + ListAtomicType atomicType = new ListAtomicType(members, rest); + Atom atom; + RecAtom rec = this.rec; + if (rec != null) { + atom = rec; + env.setRecListAtomType(rec, atomicType); + } else { + atom = env.listAtom(atomicType); + } + return this.createSemType(env, atom); + } + + private SemType createSemType(Env env, Atom atom) { + BddNode bdd = bddAtom(atom); + this.semType = basicSubType(BasicTypeCode.BT_LIST, BListSubType.createDelegate(bdd)); + return this.semType; + } + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingDefinition.java new file mode 100644 index 000000000000..82515b53f64d --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingDefinition.java @@ -0,0 +1,116 @@ +/* + * 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.Atom; +import io.ballerina.runtime.api.types.semtype.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.BddNode; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.MappingAtomicType; +import io.ballerina.runtime.api.types.semtype.RecAtom; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.Arrays; +import java.util.Comparator; + +import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; +import static io.ballerina.runtime.api.types.semtype.Builder.basicSubType; +import static io.ballerina.runtime.api.types.semtype.Builder.cellContaining; +import static io.ballerina.runtime.api.types.semtype.Builder.undef; +import static io.ballerina.runtime.api.types.semtype.Core.isNever; +import static io.ballerina.runtime.api.types.semtype.Core.union; + +public class MappingDefinition implements Definition { + + private RecAtom rec = null; + private SemType semType = null; + + @Override + public SemType getSemType(Env env) { + SemType s = this.semType; + if (s == null) { + RecAtom rec = env.recMappingAtom(); + this.rec = rec; + return this.createSemType(env, rec); + } else { + return s; + } + } + + private SemType createSemType(Env env, Atom atom) { + BddNode bdd = bddAtom(atom); + this.semType = basicSubType(BasicTypeCode.BT_MAPPING, BMappingSubType.createDelegate(bdd)); + return this.semType; + } + + public SemType defineMappingTypeWrapped(Env env, Field[] fields, SemType rest, CellAtomicType.CellMutability mut) { + assert rest != null; + BCellField[] cellFields = new BCellField[fields.length]; + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + BCellField cellField = BCellField.from(env, field, mut); + cellFields[i] = cellField; + } + SemType restCell = cellContaining(env, union(rest, undef()), + isNever(rest) ? CellAtomicType.CellMutability.CELL_MUT_NONE : mut); + return define(env, cellFields, restCell); + } + + SemType define(Env env, BCellField[] cellFields, SemType rest) { + String[] names = new String[cellFields.length]; + SemType[] types = new SemType[cellFields.length]; + sortAndSplitFields(cellFields, names, types); + MappingAtomicType atomicType = new MappingAtomicType(names, types, rest); + Atom atom; + RecAtom rec = this.rec; + if (rec != null) { + atom = rec; + env.setRecMappingAtomType(rec, atomicType); + } else { + atom = env.mappingAtom(atomicType); + } + return this.createSemType(env, atom); + } + + private void sortAndSplitFields(BCellField[] fields, String[] names, SemType[] types) { + assert fields.length == names.length && fields.length == types.length; + Arrays.sort(fields, Comparator.comparing((field) -> field.name)); + for (int i = 0; i < fields.length; i++) { + names[i] = fields[i].name; + types[i] = fields[i].type; + } + } + + public record Field(String name, SemType ty, boolean readonly, boolean optional) { + + } + + record BCellField(String name, SemType type) { + + static BCellField from(Env env, Field field, CellAtomicType.CellMutability mut) { + SemType type = field.ty; + SemType cellType = cellContaining(env, field.optional ? union(type, undef()) : type, + field.readonly ? CellAtomicType.CellMutability.CELL_MUT_NONE : mut); + BCellField cellField = new BCellField(field.name, cellType); + return cellField; + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Member.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Member.java new file mode 100644 index 000000000000..8d96cfcc543c --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Member.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.types.semtype.Builder.stringConst; + +public record Member(String name, SemType valueTy, Kind kind, Visibility visibility, boolean immutable) { + + public enum Kind { + Field, + Method; + + private static final MappingDefinition.Field FIELD = + new MappingDefinition.Field("kind", stringConst("field"), true, false); + private static final MappingDefinition.Field METHOD = + new MappingDefinition.Field("kind", stringConst("method"), true, false); + + public MappingDefinition.Field field() { + return switch (this) { + case Field -> FIELD; + case Method -> METHOD; + }; + } + } + + public enum Visibility { + Public, + Private; + + private static final SemType PUBLIC_TAG = stringConst("public"); + private static final MappingDefinition.Field PUBLIC = + new MappingDefinition.Field("visibility", PUBLIC_TAG, true, false); + private static final SemType PRIVATE_TAG = stringConst("private"); + private static final MappingDefinition.Field PRIVATE = + new MappingDefinition.Field("visibility", PRIVATE_TAG, true, false); + static final MappingDefinition.Field ALL = + new MappingDefinition.Field("visibility", Core.union(PRIVATE_TAG, PUBLIC_TAG), true, false); + + public MappingDefinition.Field field() { + return switch (this) { + case Public -> PUBLIC; + case Private -> PRIVATE; + }; + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectDefinition.java new file mode 100644 index 000000000000..0683e8cf4cb7 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectDefinition.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.Bdd; +import io.ballerina.runtime.api.types.semtype.BddNode; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.List; +import java.util.stream.Stream; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_OBJECT; +import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; +import static io.ballerina.runtime.api.types.semtype.Builder.cellContaining; +import static io.ballerina.runtime.api.types.semtype.Core.createBasicSemType; +import static io.ballerina.runtime.api.types.semtype.Core.union; +import static io.ballerina.runtime.api.types.semtype.RecAtom.createDistinctRecAtom; + +public class ObjectDefinition implements Definition { + + private final MappingDefinition mappingDefinition = new MappingDefinition(); + + @Override + public SemType getSemType(Env env) { + return objectContaining(mappingDefinition.getSemType(env)); + } + + public SemType define(Env env, ObjectQualifiers qualifiers, List members) { + CellAtomicType.CellMutability mut = qualifiers.readonly() ? CellAtomicType.CellMutability.CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + Stream memberStream = members.stream() + .map(member -> memberField(env, member, qualifiers.readonly())); + Stream qualifierStream = Stream.of(qualifiers.field(env)); + SemType mappingType = + mappingDefinition.define(env, + Stream.concat(memberStream, qualifierStream) + .map(field -> MappingDefinition.BCellField.from(env, field, mut)) + .toArray(MappingDefinition.BCellField[]::new), + restMemberType(env, mut, qualifiers.readonly())); + return objectContaining(mappingType); + } + + private SemType objectContaining(SemType mappingType) { + Bdd mappingSubTypeData = (Bdd) Core.subTypeData(mappingType, BasicTypeCode.BT_MAPPING); + return createBasicSemType(BT_OBJECT, mappingSubTypeData); + } + + private SemType restMemberType(Env env, CellAtomicType.CellMutability mut, boolean readonly) { + MappingDefinition fieldDefn = new MappingDefinition(); + SemType fieldMemberType = fieldDefn.defineMappingTypeWrapped( + env, + new MappingDefinition.Field[]{ + new MappingDefinition.Field("value", readonly ? Builder.readonlyType() : Builder.valType(), + readonly, false), + Member.Kind.Field.field(), + Member.Visibility.ALL + }, + Builder.neverType(), + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + MappingDefinition methodDefn = new MappingDefinition(); + + SemType methodMemberType = methodDefn.defineMappingTypeWrapped( + env, + new MappingDefinition.Field[]{ + new MappingDefinition.Field("value", Builder.functionType(), true, false), + Member.Kind.Method.field(), + Member.Visibility.ALL + }, + Builder.neverType(), + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + return cellContaining(env, union(fieldMemberType, methodMemberType), mut); + } + + private static MappingDefinition.Field memberField(Env env, Member member, boolean immutableObject) { + MappingDefinition md = new MappingDefinition(); + SemType semtype = md.defineMappingTypeWrapped( + env, + new MappingDefinition.Field[]{ + new MappingDefinition.Field("value", member.valueTy(), member.immutable(), false), + member.kind().field(), + member.visibility().field() + }, + Builder.neverType(), + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + return new MappingDefinition.Field(member.name(), semtype, immutableObject | member.immutable(), false); + } + + public static SemType distinct(int distinctId) { + assert distinctId >= 0; + BddNode bdd = bddAtom(createDistinctRecAtom(-distinctId - 1)); + return createBasicSemType(BT_OBJECT, bdd); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectQualifiers.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectQualifiers.java new file mode 100644 index 000000000000..086e490c429f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectQualifiers.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.types.semtype.Builder.booleanConst; +import static io.ballerina.runtime.api.types.semtype.Builder.stringConst; +import static io.ballerina.runtime.api.types.semtype.Core.union; + +public record ObjectQualifiers(boolean isolated, boolean readonly, NetworkQualifier networkQualifier) { + + public MappingDefinition.Field field(Env env) { + MappingDefinition md = new MappingDefinition(); + MappingDefinition.Field isolatedField = + new MappingDefinition.Field("isolated", isolated ? booleanConst(true) : Builder.booleanType(), + true, false); + MappingDefinition.Field networkField = networkQualifier.field(); + SemType ty = md.defineMappingTypeWrapped(env, new MappingDefinition.Field[]{isolatedField, networkField}, + Builder.neverType(), CellAtomicType.CellMutability.CELL_MUT_NONE); + return new MappingDefinition.Field("$qualifiers", ty, true, false); + } + + public enum NetworkQualifier { + Client, + Service, + None; + + private static final SemType CLIENT_TAG = stringConst("client"); + private static final MappingDefinition.Field CLIENT = + new MappingDefinition.Field("network", CLIENT_TAG, true, false); + + private static final SemType SERVICE_TAG = stringConst("service"); + private static final MappingDefinition.Field SERVICE = + new MappingDefinition.Field("network", SERVICE_TAG, true, false); + + // Object can't be both client and service, which is enforced by the enum. We are using a union here so that + // if this is none it matches both + private static final MappingDefinition.Field NONE = + new MappingDefinition.Field("network", union(CLIENT_TAG, SERVICE_TAG), true, false); + + private MappingDefinition.Field field() { + return switch (this) { + case Client -> CLIENT; + case Service -> SERVICE; + case None -> NONE; + }; + } + } +} 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..7fe00d5fd286 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/PureSemType.java @@ -0,0 +1,43 @@ +/* + * 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 { + + private final int index; + private static int nextIndex; + + public PureSemType(int all, int some, SubType[] subTypeData) { + super(all, some, subTypeData); + index = nextIndex++; + } + + public PureSemType(int all) { + super(all); + index = nextIndex++; + } +} 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..d55a20832818 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubTypeData.java @@ -0,0 +1,29 @@ +/* + * 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 + */ +// TODO: move this to api +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..0acffd7f3a7f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePairIterator.java @@ -0,0 +1,69 @@ +/* + * 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 some; + private final SemType t1; + private final SemType t2; + + SubtypePairIterator(SemType t1, SemType t2, int some) { + this.some = some; + this.t1 = t1; + this.t2 = t2; + incrementIndex(); + } + + @Override + public boolean hasNext() { + return index < maxIndex; + } + + private void incrementIndex() { + int rest = some >> index; + int offset = Integer.numberOfTrailingZeros(rest); + index += offset; + } + + @Override + public SubtypePair next() { + SubType subType1 = t1.subTypeByCode(index); + SubType subType2 = t2.subTypeByCode(index); + assert (subType1 == null || subType2 == null) || (subType1.getClass().equals(subType2.getClass())); + 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/AbstractArrayValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractArrayValue.java index 841c6a0b3248..a4aa7fcb71a0 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractArrayValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractArrayValue.java @@ -20,6 +20,10 @@ import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.types.Type; +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.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.internal.IteratorUtils; @@ -271,6 +275,12 @@ protected void prepareForAddForcefully(int intIndex, int currentArraySize) { resetSize(intIndex); } + @Override + public SemType widenedType(Context cx) { + SemType semType = Builder.from(cx, getType()); + return Core.intersect(semType, Builder.listType()); + } + /** * {@code {@link ArrayIterator}} provides iterator implementation for Ballerina array values. * diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractObjectValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractObjectValue.java index 068b97bb03a4..8afc8824587b 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractObjectValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractObjectValue.java @@ -23,6 +23,7 @@ import io.ballerina.runtime.api.types.ObjectType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeId; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; @@ -61,6 +62,7 @@ public abstract class AbstractObjectValue implements ObjectValue { private BTypedesc typedesc; private final BObjectType objectType; private final Type type; + private SemType shape; private final HashMap nativeData = new HashMap<>(); @@ -232,4 +234,12 @@ private void checkFieldUpdateType(String fieldName, Object value) { ErrorHelper.getErrorDetails(ErrorCodes.INVALID_OBJECT_FIELD_VALUE_ERROR, fieldName, fieldType, TypeChecker.getType(value))); } + + public final SemType shapeOf() { + return shape; + } + + public final void cacheShape(SemType semType) { + this.shape = semType; + } } 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..f2e9b65fe2e4 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 @@ -24,6 +24,7 @@ import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.ArrayType.ArrayState; 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.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; @@ -86,6 +87,8 @@ public class ArrayValueImpl extends AbstractArrayValue { private double[] floatValues; private BString[] bStringValues; private BTypedesc typedesc; + + private SemType shape; // ------------------------ Constructors ------------------------------------------------------------------- public ArrayValueImpl(Object[] values, ArrayType type) { @@ -614,15 +617,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 +635,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 +662,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 +692,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 +1249,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); } @@ -1407,4 +1410,14 @@ private int calculateHashCode(List visited) { } return result; } + + @Override + public void cacheShape(SemType semType) { + shape = semType; + } + + @Override + public SemType shapeOf() { + return shape; + } } 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/ErrorValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ErrorValue.java index 6a3caa4b1c17..4abbc5f96dbc 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ErrorValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ErrorValue.java @@ -46,7 +46,6 @@ import java.util.Set; import java.util.StringJoiner; -import static io.ballerina.runtime.api.PredefinedTypes.TYPE_MAP; import static io.ballerina.runtime.api.constants.RuntimeConstants.BLANG_SRC_FILE_SUFFIX; import static io.ballerina.runtime.api.constants.RuntimeConstants.DOT; import static io.ballerina.runtime.api.constants.RuntimeConstants.MODULE_INIT_CLASS_NAME; @@ -83,7 +82,7 @@ public class ErrorValue extends BError implements RefValue { private static final String STOP_FUNCTION_SUFFIX = "."; public ErrorValue(BString message) { - this(new BErrorType(TypeConstants.ERROR, PredefinedTypes.TYPE_ERROR.getPackage(), TYPE_MAP), + this(new BErrorType(TypeConstants.ERROR, PredefinedTypes.TYPE_ERROR.getPackage(), PredefinedTypes.TYPE_DETAIL), message, null, new MapValueImpl<>(PredefinedTypes.TYPE_ERROR_DETAIL)); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java index 44f8b914744a..5b0effa2f643 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java @@ -20,6 +20,9 @@ import io.ballerina.runtime.api.async.StrandMetadata; import io.ballerina.runtime.api.constants.RuntimeConstants; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.values.BFunctionPointer; import io.ballerina.runtime.api.values.BFuture; import io.ballerina.runtime.api.values.BLink; @@ -124,4 +127,9 @@ public BTypedesc getTypedesc() { public String toString() { return RuntimeConstants.EMPTY; } + + @Override + public SemType widenedType(Context cx) { + return Builder.functionType(); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValue.java index 262173eedb23..77752b5f1887 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValue.java @@ -17,6 +17,10 @@ */ package io.ballerina.runtime.internal.values; +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.BMap; /** @@ -34,4 +38,9 @@ */ public interface MapValue extends RefValue, CollectionValue, BMap { + @Override + default SemType widenedType(Context cx) { + SemType semType = Builder.from(cx, getType()); + return Core.intersect(semType, Builder.mappingType()); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValueImpl.java index eb3451278939..f5ae557063b7 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValueImpl.java @@ -22,6 +22,7 @@ import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.types.Field; 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.BArray; import io.ballerina.runtime.api.values.BError; @@ -100,6 +101,7 @@ public class MapValueImpl extends LinkedHashMap implements RefValue, private Type referredType; private final Map nativeData = new HashMap<>(); private Type iteratorNextReturnType; + private SemType shape; public MapValueImpl(TypedescValue typedesc) { this(typedesc.getDescribingType()); @@ -709,4 +711,14 @@ public Type getIteratorNextReturnType() { protected V putValue(K key, V value) { return super.put(key, value); } + + @Override + public void cacheShape(SemType semType) { + shape = semType; + } + + @Override + public SemType shapeOf() { + return shape; + } } 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/io/ballerina/runtime/internal/values/TupleValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java index d0939c5f7158..7e3c0b8e75c1 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java @@ -21,6 +21,7 @@ import io.ballerina.runtime.api.creators.ErrorCreator; 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.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; @@ -74,6 +75,7 @@ public class TupleValueImpl extends AbstractArrayValue { private final boolean hasRestElement; // cached value for ease of access private BTypedesc typedesc; private TypedescValueImpl inherentType; + private SemType shape; // ------------------------ Constructors ------------------------------------------------------------------- public TupleValueImpl(Object[] values, TupleType type) { @@ -827,4 +829,14 @@ private void resetSize(int index) { size = index + 1; } } + + @Override + public void cacheShape(SemType semType) { + shape = semType; + } + + @Override + public SemType shapeOf() { + return shape; + } } 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/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/semtype/CoreTests.java b/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/semtype/CoreTests.java new file mode 100644 index 000000000000..07d6d88114b1 --- /dev/null +++ b/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/semtype/CoreTests.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.test.semtype; + +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; +import org.testng.annotations.Test; + +// These are temporary sanity checks until we have actual types using cell types are implemented +public class CoreTests { + + @Test + public void testCellTypes() { + Env env = Env.getInstance(); + Context cx = Context.from(env); + SemType intTy = Builder.intType(); + SemType readonlyInt = Builder.cellContaining(env, intTy, CellAtomicType.CellMutability.CELL_MUT_NONE); + assert Core.isSubType(cx, readonlyInt, readonlyInt); + SemType mutableInt = Builder.cellContaining(env, intTy, CellAtomicType.CellMutability.CELL_MUT_UNLIMITED); + assert Core.isSubType(cx, mutableInt, mutableInt); + assert Core.isSubType(cx, readonlyInt, mutableInt); + assert !Core.isSubType(cx, mutableInt, readonlyInt); + } + + @Test + public void testCellTypeCaching() { + Env env = Env.getInstance(); + SemType intTy = Builder.intType(); + SemType readonlyInt1 = Builder.cellContaining(env, intTy, CellAtomicType.CellMutability.CELL_MUT_NONE); + SemType readonlyInt2 = Builder.cellContaining(env, intTy, CellAtomicType.CellMutability.CELL_MUT_NONE); + assert readonlyInt1 == readonlyInt2; + } + + @Test + public void testSimpleList() { + Env env = Env.getInstance(); + SemType intTy = Builder.intType(); + // int[] + ListDefinition ld = new ListDefinition(); + SemType intListTy = + ld.defineListTypeWrapped(env, new SemType[0], 0, intTy, + CellAtomicType.CellMutability.CELL_MUT_UNLIMITED); + + // int[1] + ListDefinition ld1 = new ListDefinition(); + SemType[] members = {intTy}; + SemType intListTy1 = + ld1.defineListTypeWrapped(env, members, 1, Builder.neverType(), + CellAtomicType.CellMutability.CELL_MUT_UNLIMITED); + + Context cx = Context.from(env); + assert Core.isSubType(cx, intListTy1, intListTy); + } +} diff --git a/bvm/ballerina-runtime/src/test/resources/testng.xml b/bvm/ballerina-runtime/src/test/resources/testng.xml index 605dbab4dee3..ea13455011ff 100644 --- a/bvm/ballerina-runtime/src/test/resources/testng.xml +++ b/bvm/ballerina-runtime/src/test/resources/testng.xml @@ -20,7 +20,7 @@ - + diff --git a/langlib/lang.error/src/main/java/org/ballerinalang/langlib/error/StackTrace.java b/langlib/lang.error/src/main/java/org/ballerinalang/langlib/error/StackTrace.java index 398f210a2517..a535f641d65c 100644 --- a/langlib/lang.error/src/main/java/org/ballerinalang/langlib/error/StackTrace.java +++ b/langlib/lang.error/src/main/java/org/ballerinalang/langlib/error/StackTrace.java @@ -25,6 +25,7 @@ import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.MethodType; import io.ballerina.runtime.api.types.ObjectType; +import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; @@ -47,6 +48,8 @@ import static io.ballerina.runtime.api.constants.RuntimeConstants.DOT; import static io.ballerina.runtime.api.constants.RuntimeConstants.EMPTY; import static io.ballerina.runtime.api.constants.RuntimeConstants.FILE_NAME_PERIOD_SEPARATOR; +import static io.ballerina.runtime.api.flags.SymbolFlags.OPTIONAL; +import static io.ballerina.runtime.api.flags.SymbolFlags.PUBLIC; import static io.ballerina.runtime.api.values.BError.CALL_STACK_ELEMENT; /** @@ -56,21 +59,34 @@ */ public class StackTrace { + private static final ObjectType CALLSTACK_TYPE = createCallStackType(); + public static BObject stackTrace(BError value) { + CallStack callStack = new CallStack(CALLSTACK_TYPE); + callStack.callStack = getCallStackArray(value.getStackTrace()); + callStack.callStack.freezeDirect(); + return callStack; + } + + private static ObjectType createCallStackType() { + Module module = new Module("ballerina", "lang.error", null); + RecordType callStackElementType = + TypeCreator.createRecordType("CallStackElement", module, 0, Map.of( + "callableName", TypeCreator.createField(PredefinedTypes.TYPE_STRING, "callableName", 0), + "moduleName", TypeCreator.createField(PredefinedTypes.TYPE_STRING, "moduleName", OPTIONAL), + "fileName", TypeCreator.createField(PredefinedTypes.TYPE_STRING, "fileName", 0), + "lineNumber", TypeCreator.createField(PredefinedTypes.TYPE_INT, "lineNumber", 0) + ), PredefinedTypes.TYPE_NEVER, false, 0); + ObjectType callStackObjType = TypeCreator - .createObjectType("CallStack", new Module("ballerina", "lang.error", null), 0); + .createObjectType("CallStack", module, 0); callStackObjType.setMethods(new MethodType[]{}); callStackObjType .setFields(Collections.singletonMap("callStack", - TypeCreator.createField(TypeCreator.createArrayType( - PredefinedTypes.TYPE_ANY), - null, 0))); - - CallStack callStack = new CallStack(callStackObjType); - callStack.callStack = getCallStackArray(value.getStackTrace()); - callStack.callStack.freezeDirect(); - return callStack; + TypeCreator.createField(TypeCreator.createArrayType(callStackElementType), "callStack", + PUBLIC))); + return callStackObjType; } private static BArray getCallStackArray(StackTraceElement[] stackTrace) { 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/semtypes/src/main/java/io/ballerina/types/typeops/ListOps.java b/semtypes/src/main/java/io/ballerina/types/typeops/ListOps.java index a0e6d1fde971..406bd48e91e3 100644 --- a/semtypes/src/main/java/io/ballerina/types/typeops/ListOps.java +++ b/semtypes/src/main/java/io/ballerina/types/typeops/ListOps.java @@ -197,7 +197,7 @@ static List listSamples(Context cx, FixedLengthArray members, SemType r } for (int i = 0; i < nNeg; i++) { // Be careful to avoid integer overflow. - if (lastBoundary > Long.MAX_VALUE - i) { + if (lastBoundary > Integer.MAX_VALUE - i) { break; } indices.add(lastBoundary + i); 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..23a4cceb518b --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerSemTypeResolver.java @@ -0,0 +1,700 @@ +/* + * 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.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 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..116d114445be --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerTypeTestEnv.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.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); + } + + @Override + public Object getInnerEnv() { + return env; + } +} 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..fc97f03ed2aa --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeSemTypeResolver.java @@ -0,0 +1,569 @@ +/* + * 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.CellAtomicType; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.internal.types.semtype.Definition; +import io.ballerina.runtime.internal.types.semtype.ErrorUtils; +import io.ballerina.runtime.internal.types.semtype.FunctionDefinition; +import io.ballerina.runtime.internal.types.semtype.FunctionQualifiers; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; +import io.ballerina.runtime.internal.types.semtype.MappingDefinition; +import io.ballerina.runtime.internal.types.semtype.Member; +import io.ballerina.runtime.internal.types.semtype.ObjectDefinition; +import io.ballerina.runtime.internal.types.semtype.ObjectQualifiers; +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.BLangLiteral; +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.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.math.BigDecimal; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +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; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_LIMITED; +import static io.ballerina.runtime.api.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; + +class RuntimeSemTypeResolver extends SemTypeResolver { + + private static final SemType[] EMPTY_SEMTYPE_ARR = {}; + Map attachedSemType = new HashMap<>(); + Map semTypeMemo = new HashMap<>(); + Map attachedDefinitions = 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, TypeNode 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); + case ARRAY_TYPE -> resolveArrayTypeDesc(cx, mod, defn, depth, (BLangArrayType) td); + case TUPLE_TYPE_NODE -> resolveTupleTypeDesc(cx, mod, defn, depth, (BLangTupleTypeNode) td); + case CONSTRAINED_TYPE -> resolveConstrainedTypeDesc(cx, mod, defn, depth, (BLangConstrainedType) td); + case RECORD_TYPE -> resolveRecordTypeDesc(cx, mod, defn, depth, (BLangRecordTypeNode) td); + case FUNCTION_TYPE -> resolveFunctionTypeDesc(cx, mod, defn, depth, (BLangFunctionTypeNode) td); + case OBJECT_TYPE -> resolveObjectTypeDesc(cx, mod, defn, depth, (BLangObjectTypeNode) td); + case ERROR_TYPE -> resolveErrorTypeDesc(cx, mod, defn, depth, (BLangErrorType) td); + default -> throw new UnsupportedOperationException("type not implemented: " + td.getKind()); + }; + } + + private SemType resolveErrorTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangErrorType td) { + SemType innerType = createErrorType(cx, mod, defn, depth, td); + if (td.flagSet.contains(Flag.DISTINCT)) { + Env env = (Env) cx.getInnerEnv(); + return getDistinctErrorType(env, innerType); + } + return innerType; + } + + private static SemType getDistinctErrorType(Env env, SemType innerType) { + return Core.intersect(ErrorUtils.errorDistinct(env.distinctAtomCountGetAndIncrement()), innerType); + } + + private SemType createErrorType(TypeTestContext cx, Map mod, BLangTypeDefinition defn, + int depth, BLangErrorType td) { + if (td.detailType == null) { + return Builder.errorType(); + } else { + SemType detailType = resolveTypeDesc(cx, mod, defn, depth + 1, td.detailType); + return ErrorUtils.errorDetail(detailType); + } + } + + private SemType resolveObjectTypeDesc(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 SemType resolveNonDistinctObject(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangObjectTypeNode td) { + Env env = (Env) cx.getInnerEnv(); + Definition attachedDefinition = attachedDefinitions.get(td); + if (attachedDefinition != null) { + return attachedDefinition.getSemType(env); + } + ObjectDefinition od = new ObjectDefinition(); + attachedDefinitions.put(td, od); + 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 = resolveFunctionType(cx, mod, defn, depth + 1, method); + return new Member(method.name.value, ty, Member.Kind.Method, visibility, true); + }); + List members = Stream.concat(fieldStream, methodStream).toList(); + ObjectQualifiers qualifiers = getQualifiers(td); + return od.define(env, qualifiers, members); + } + + private 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); + } + + private SemType resolveFunctionType(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, + int depth, BLangFunction functionType) { + Env env = (Env) cx.getInnerEnv(); + Definition attached = attachedDefinitions.get(functionType); + if (attached != null) { + return attached.getSemType(env); + } + FunctionDefinition fd = new FunctionDefinition(); + attachedDefinitions.put(functionType, fd); + SemType[] params = functionType.getParameters().stream() + .map(paramVar -> resolveTypeDesc(cx, mod, defn, depth + 1, paramVar.typeNode)).toArray(SemType[]::new); + SemType rest; + if (functionType.getRestParameters() == null) { + rest = Builder.neverType(); + } 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()) : Builder.nilType(); + ListDefinition paramListDefinition = new ListDefinition(); + FunctionQualifiers qualifiers = FunctionQualifiers.create(functionType.flagSet.contains(Flag.ISOLATED), + functionType.flagSet.contains(Flag.TRANSACTIONAL)); + return fd.define(env, paramListDefinition.defineListTypeWrapped(env, params, params.length, rest, + CellAtomicType.CellMutability.CELL_MUT_NONE), returnType, qualifiers); + } + + private SemType getDistinctObjectType(Env env, SemType innerType) { + return Core.intersect(ObjectDefinition.distinct(env.distinctAtomCountGetAndIncrement()), innerType); + } + + private SemType resolveFunctionTypeDesc(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)) { + FunctionDefinition fd = new FunctionDefinition(); + return fd.define(env, Builder.neverType(), Builder.valType(), + FunctionQualifiers.create( + td.flagSet.contains(Flag.ISOLATED), + td.flagSet.contains(Flag.TRANSACTIONAL))); + } + return Builder.functionType(); + } + Definition attachedDefinition = attachedDefinitions.get(td); + if (attachedDefinition != null) { + return attachedDefinition.getSemType(env); + } + FunctionDefinition fd = new FunctionDefinition(); + attachedDefinitions.put(td, fd); + List params = + td.params.stream().map(param -> resolveTypeDesc(cx, mod, defn, depth + 1, param.typeNode)) + .toList(); + SemType rest; + if (td.restParam == null) { + rest = Builder.neverType(); + } 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) : + Builder.nilType(); + ListDefinition paramListDefinition = new ListDefinition(); + return fd.define(env, + paramListDefinition.defineListTypeWrapped(env, params.toArray(SemType[]::new), params.size(), rest, + CELL_MUT_NONE), + returnType, + FunctionQualifiers.create(td.flagSet.contains(Flag.ISOLATED), td.flagSet.contains(Flag.TRANSACTIONAL))); + } + + private boolean isFunctionTop(BLangFunctionTypeNode td) { + return td.params.isEmpty() && td.restParam == null && td.returnTypeNode == null; + } + + private SemType resolveRecordTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangRecordTypeNode td) { + Env env = (Env) cx.getInnerEnv(); + Definition attachedDefinition = attachedDefinitions.get(td); + if (attachedDefinition != null) { + return attachedDefinition.getSemType(env); + } + + MappingDefinition md = new MappingDefinition(); + attachedDefinitions.put(td, md); + + MappingDefinition.Field[] fields = new MappingDefinition.Field[td.fields.size()]; + for (int i = 0; i < td.fields.size(); i++) { + BLangSimpleVariable field = td.fields.get(i); + SemType type = resolveTypeDesc(cx, mod, defn, depth + 1, field.typeNode); + if (Core.isNever(type)) { + throw new IllegalStateException("record field can't be never"); + } + fields[i] = + new MappingDefinition.Field(field.name.value, type, field.flagSet.contains(Flag.READONLY), + field.flagSet.contains(Flag.OPTIONAL)); + } + + SemType rest; + if (!td.isSealed() && td.getRestFieldType() == null) { + rest = Builder.anyDataType((Context) cx.getInnerContext()); + } else { + rest = resolveTypeDesc(cx, mod, defn, depth + 1, td.getRestFieldType()); + } + if (rest == null) { + rest = Builder.neverType(); + } + return md.defineMappingTypeWrapped((Env) cx.getInnerEnv(), fields, rest, CELL_MUT_LIMITED); + } + + private SemType resolveConstrainedTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangConstrainedType td) { + BLangBuiltInRefTypeNode refTypeNode = (BLangBuiltInRefTypeNode) td.getType(); + return switch (refTypeNode.typeKind) { + case MAP -> resolveMapTypeDesc(cx, mod, defn, depth, td); + default -> throw new UnsupportedOperationException( + "Constrained type not implemented: " + refTypeNode.typeKind); + }; + } + + private SemType resolveMapTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangConstrainedType td) { + Env env = (Env) cx.getInnerEnv(); + Definition attachedDefinition = attachedDefinitions.get(td); + if (attachedDefinition != null) { + return attachedDefinition.getSemType(env); + } + + MappingDefinition md = new MappingDefinition(); + attachedDefinitions.put(td, md); + + SemType rest = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + + return md.defineMappingTypeWrapped(env, new MappingDefinition.Field[0], rest, CELL_MUT_LIMITED); + } + + private SemType resolveTupleTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangTupleTypeNode td) { + Env env = (Env) cx.getInnerEnv(); + Definition attachedDefinition = attachedDefinitions.get(td); + if (attachedDefinition != null) { + return attachedDefinition.getSemType(env); + } + ListDefinition ld = new ListDefinition(); + attachedDefinitions.put(td, ld); + SemType[] memberSemTypes = td.members.stream() + .map(member -> resolveTypeDesc(cx, mod, defn, depth + 1, member.typeNode)) + .toArray(SemType[]::new); + SemType rest = + td.restParamType != null ? resolveTypeDesc(cx, mod, defn, depth + 1, td.restParamType) : + Builder.neverType(); + return ld.defineListTypeWrapped(env, memberSemTypes, memberSemTypes.length, rest, CELL_MUT_LIMITED); + } + + private SemType resolveArrayTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangArrayType td) { + Definition attachedDefinition = attachedDefinitions.get(td); + if (attachedDefinition != null) { + return attachedDefinition.getSemType((Env) cx.getInnerEnv()); + } + + ListDefinition ld = new ListDefinition(); + attachedDefinitions.put(td, 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 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) { + SemType[] members = {eType}; + return ld.defineListTypeWrapped(env, members, Math.abs(size), Builder.neverType(), CELL_MUT_LIMITED); + } else { + return ld.defineListTypeWrapped(env, EMPTY_SEMTYPE_ARR, 0, eType, CELL_MUT_LIMITED); + } + } + + + 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) { + SemType ty = resolveTypeDefnRec(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 resolveTypeDefnRec(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, Builder.objectType())) { + return getDistinctObjectType(env, innerType); + } else if (Core.isSubtypeSimple(innerType, Builder.errorType())) { + 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" -> 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(); + case READONLY -> Builder.readonlyType(); + case ANY -> Builder.anyType(); + case ANYDATA -> Builder.anyDataType((Context) cx.getInnerContext()); + 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..cc11cc8e0161 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestAPI.java @@ -0,0 +1,82 @@ +/* + * 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.ListProj; +import io.ballerina.runtime.api.types.semtype.MappingProj; +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(from(cx), t1, t2); + } + + private static Context from(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) { + return Core.isSubtypeSimple(t, Builder.listType()); + } + + @Override + public boolean isMapType(SemType t) { + return Core.isSubtypeSimple(t, Builder.mappingType()); + } + + @Override + public SemType intConst(long l) { + return Builder.intConst(l); + } + + @Override + public SemType mappingMemberTypeInnerVal(TypeTestContext context, SemType t, SemType key) { + return MappingProj.mappingMemberTypeInnerVal(from(context), t, key); + } + + @Override + public SemType listProj(TypeTestContext context, SemType t, SemType key) { + return ListProj.listProjInnerVal(from(context), t, key); + } + + @Override + public SemType listMemberType(TypeTestContext context, SemType t, SemType key) { + return Core.listMemberTypeInnerVal(from(context), t, key); + } +} 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..add06c8db8c5 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestContext.java @@ -0,0 +1,53 @@ +/* + * 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.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +public final class RuntimeTypeTestContext implements TypeTestContext { + + private final TypeTestEnv env; + private final Context cx; + + private RuntimeTypeTestContext(TypeTestEnv env) { + this.env = env; + this.cx = Context.from((Env) env.getInnerEnv()); + } + + public static synchronized RuntimeTypeTestContext from(TypeTestEnv env) { + return new RuntimeTypeTestContext(env); + } + + @Override + public TypeTestEnv getEnv() { + return env; + } + + @Override + public Object getInnerEnv() { + return env.getInnerEnv(); + } + + @Override + public Object getInnerContext() { + return cx; + } +} 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..2592d6ba5565 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeTypeTestEnv.java @@ -0,0 +1,54 @@ +/* + * 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.Env; +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 final Env env; + + private RuntimeTypeTestEnv(Env env) { + this.env = env; + } + + public static synchronized RuntimeTypeTestEnv from(Env env) { + return new RuntimeTypeTestEnv(env); + } + + @Override + public Map getTypeNameSemTypeMap() { + return typeMap; + } + + @Override + public void addTypeDef(String value, SemType semtype) { + typeMap.put(value, semtype); + } + + @Override + public Object getInnerEnv() { + return env; + } +} 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..0d9054bd3235 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,334 +15,33 @@ * 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 { - - private final Map attachedDefinitions = new HashMap<>(); - - void defineSemTypes(List moduleDefs, Context 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); - } - } - } - - 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); - } +public abstract class SemTypeResolver { - 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) { + protected static int from(Map mod, BLangNode expr) { if (expr instanceof BLangLiteral literal) { - return listSize((Number) literal.value); + return SemTypeResolver.listSize((Number) literal.value); } else if (expr instanceof BLangSimpleVarRef varRef) { String varName = varRef.variableName.value; - return from(mod, mod.get(varName)); + return SemTypeResolver.from(mod, mod.get(varName)); } else if (expr instanceof BLangConstant constant) { - return listSize((Number) constant.symbol.value.value); + return SemTypeResolver.listSize((Number) constant.symbol.value.value); } throw new UnsupportedOperationException("Unsupported expr kind " + expr.getKind()); } @@ -354,340 +53,28 @@ private static int listSize(Number size) { 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); - } - 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()); + public void defineSemTypes(List moduleDefs, TypeTestContext cx) { + Map modTable = new LinkedHashMap<>(); + for (BLangNode typeAndClassDef : moduleDefs) { + modTable.put(getTypeOrClassName(typeAndClassDef), typeAndClassDef); } - return u; - } - - private SemType resolveSingletonType(BLangLiteral literal) { - return resolveSingletonType(literal.value, literal.getDeterminedType().getKind()); - } + modTable = Collections.unmodifiableMap(modTable); - 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. + 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); } - 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; - } - - if (td.defn != null) { - return td.defn.getSemType(cx.env); - } + protected abstract void resolveConstant(TypeTestContext cx, + Map modTable, BLangConstant constant); - 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..1b46e8537c52 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 @@ -17,6 +17,7 @@ */ package io.ballerina.semtype.port.test; +import io.ballerina.runtime.api.types.semtype.Env; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.internal.ValueComparisonUtils; import io.ballerina.tools.diagnostics.Diagnostic; @@ -41,11 +42,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 +105,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 +159,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 +212,126 @@ 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 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" + )); + return balFiles.stream() + .filter(tableFilter) + .filter(xmlFilter) + .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(Env.getInstance()); + 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 +357,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 +376,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..3a1afd44326f --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/TypeTestEnv.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.semtype.port.test; + +import java.util.Map; + +public interface TypeTestEnv { + + Map getTypeNameSemTypeMap(); + + void addTypeDef(String value, SemType semtype); + + Object getInnerEnv(); +} 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/error1-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/error1-tv.bal new file mode 100644 index 000000000000..8a188010c68f --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/error1-tv.bal @@ -0,0 +1,9 @@ +// @type EL < E +// @type ER1 < E +// @type ER1 = ER2 +// @type EL <> ER1 +// @type ER2 < E +type EL error; +type ER1 error; +type ER2 error; +type E error; 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..e11c0d03170c --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/float-tv.bal @@ -0,0 +1,22 @@ +// @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/mapping-basic-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/mapping-basic-tv.bal new file mode 100644 index 000000000000..a17a0d5da75d --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/mapping-basic-tv.bal @@ -0,0 +1,3 @@ +// @type M_INT < M_ANY +type M_ANY map; +type M_INT map; \ No newline at end of file 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; diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/typecast/type-casting.bal b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/typecast/type-casting.bal index cb1f503e661f..6d28ca85a3e5 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/typecast/type-casting.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/typecast/type-casting.bal @@ -1022,8 +1022,7 @@ function testCastOfReadonlyRecordNegative() { Bar|error b = trap a; assertEquality(true, b is error); error err = b; - string errMsg = "incompatible types: '(Foo & readonly)' cannot be cast to 'Bar': " + - "\n\t\tfield 'arr' in record 'Bar' should be of type 'byte[]', found '[1,2,300]'"; + string errMsg = "incompatible types: '(Foo & readonly)' cannot be cast to 'Bar'"; assertEquality("{ballerina}TypeCastError", err.message()); assertEquality(errMsg, checkpanic err.detail()["message"]); } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/record/record_project_closed_rec_equiv/Ballerina.toml b/tests/jballerina-unit-test/src/test/resources/test-src/record/record_project_closed_rec_equiv/Ballerina.toml index b7ec0186ad78..bbccc659c584 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/record/record_project_closed_rec_equiv/Ballerina.toml +++ b/tests/jballerina-unit-test/src/test/resources/test-src/record/record_project_closed_rec_equiv/Ballerina.toml @@ -1,4 +1,4 @@ [package] org = "testorg" -name = "recordproject" +name = "closedrecordproject" version = "1.0.0" diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/record/record_project_closed_rec_equiv/closed_record_equivalency.bal b/tests/jballerina-unit-test/src/test/resources/test-src/record/record_project_closed_rec_equiv/closed_record_equivalency.bal index 6cb5e6e63325..2619d1a62c10 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/record/record_project_closed_rec_equiv/closed_record_equivalency.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/record/record_project_closed_rec_equiv/closed_record_equivalency.bal @@ -14,10 +14,10 @@ // specific language governing permissions and limitations // under the License. -import recordproject.eq; -import recordproject.eq2; -import recordproject.req; -import recordproject.req2; +import closedrecordproject.eq; +import closedrecordproject.eq2; +import closedrecordproject.req; +import closedrecordproject.req2; public type person1 record {| int age = 0;