diff --git a/ballerina-shell/modules/shell-cli/src/test/resources/testcases/operations.cast.json b/ballerina-shell/modules/shell-cli/src/test/resources/testcases/operations.cast.json index a687fab378e1..bcfb9d35a014 100644 --- a/ballerina-shell/modules/shell-cli/src/test/resources/testcases/operations.cast.json +++ b/ballerina-shell/modules/shell-cli/src/test/resources/testcases/operations.cast.json @@ -1,15 +1,15 @@ [ { "description": "Define types.", - "code": "type Person record { string name; int age; }; type Employee record { string name; int age; int empNo; }; type Department record { string code; };" + "code": "type PersonOP record { string name; int age; }; type EmployeeOP record { string name; int age; int empNo; }; type DepartmentOP record { string code; };" }, { "description": "Define employee.", - "code": "Employee employee = {name: \"Jane Doe\", age: 25, empNo: 1};" + "code": "EmployeeOP employee = {name: \"Jane Doe\", age: 25, empNo: 1};" }, { "description": "Cas employee to person.", - "code": "Person person = employee;" + "code": "PersonOP person = employee;" }, { "description": "Cas employee to person - get value.", @@ -18,7 +18,7 @@ }, { "description": "Recast back to employee.", - "code": "Employee employeeTwo = person;" + "code": "EmployeeOP employeeTwo = person;" }, { "description": "Recast back to employee - get value.", diff --git a/ballerina-shell/modules/shell-cli/src/test/resources/testcases/operations.equality.json b/ballerina-shell/modules/shell-cli/src/test/resources/testcases/operations.equality.json index 75c3e634644d..fe87ea575eef 100644 --- a/ballerina-shell/modules/shell-cli/src/test/resources/testcases/operations.equality.json +++ b/ballerina-shell/modules/shell-cli/src/test/resources/testcases/operations.equality.json @@ -1,15 +1,15 @@ [ { "description": "Define types.", - "code": "type Employee record { string name; int id; }; type Person record { string name; };" + "code": "type EmployeeEQ record { string name; int id; }; type PersonEQ record { string name; };" }, { "description": "Define employee.", - "code": "final Employee moduleEmployee = {name: \"John\", id: 2102};" + "code": "final EmployeeEQ moduleEmployee = {name: \"John\", id: 2102};" }, { "description": "Define module ref getter.", - "code": "function getModuleEmployee() returns Employee { return moduleEmployee; }" + "code": "function getModuleEmployee() returns EmployeeEQ { return moduleEmployee; }" }, { "description": "Equality ==.", @@ -49,7 +49,7 @@ }, { "description": "Deep inequality in records.", - "code": "Employee e1 = {name: \"Jane\", id: 1100}; Employee e2 = {name: \"Jane\", id: 1100};" + "code": "EmployeeEQ e1 = {name: \"Jane\", id: 1100}; EmployeeEQ e2 = {name: \"Jane\", id: 1100};" }, { "description": "Deep inequality in records. - get value", @@ -58,7 +58,7 @@ }, { "description": "Deep equality in records.", - "code": "Employee e3 = {name: \"Anne\", id: 1100};" + "code": "EmployeeEQ e3 = {name: \"Anne\", id: 1100};" }, { "description": "Deep equality in records. - get value", @@ -67,7 +67,7 @@ }, { "description": "Reference equality ===.", - "code": "Employee e4 = getModuleEmployee(); Person e5 = getModuleEmployee();" + "code": "EmployeeEQ e4 = getModuleEmployee(); PersonEQ e5 = getModuleEmployee();" }, { "description": "Reference equality ===. - get value", diff --git a/ballerina-shell/modules/shell-core/src/test/resources/testcases/evaluator/operations.clone.json b/ballerina-shell/modules/shell-core/src/test/resources/testcases/evaluator/operations.clone.json index 9c13311bedf4..2c6d6586bd66 100644 --- a/ballerina-shell/modules/shell-core/src/test/resources/testcases/evaluator/operations.clone.json +++ b/ballerina-shell/modules/shell-core/src/test/resources/testcases/evaluator/operations.clone.json @@ -1,19 +1,19 @@ [ { "description": "Define types.", - "code": "type Address record { string country; string state; string city; string street; }; type Person record { string name; int age; boolean married; float salary; Address address; };" + "code": "type AddressCloneTest record { string country; string state; string city; string street; }; type PersonCloneTest record { string name; int age; boolean married; float salary; AddressCloneTest address; };" }, { "description": "Define address.", - "code": "Address address = { country: \"USA\", state: \"NC\", city: \"Raleigh\", street: \"Daniels St\" };" + "code": "AddressCloneTest address = { country: \"USA\", state: \"NC\", city: \"Raleigh\", street: \"Daniels St\" };" }, { "description": "Define person.", - "code": "Person person = { name: \"Alex\", age: 24, married: false, salary: 8000.0, address: address };" + "code": "PersonCloneTest person = { name: \"Alex\", age: 24, married: false, salary: 8000.0, address: address };" }, { "description": "Clone operation.", - "code": "Person result = person.clone();" + "code": "PersonCloneTest result = person.clone();" }, { "description": "Check reference equality.", diff --git a/ballerina-shell/modules/shell-core/src/test/resources/testcases/evaluator/operations.immutable.json b/ballerina-shell/modules/shell-core/src/test/resources/testcases/evaluator/operations.immutable.json index 5e1fbfa5298b..870e6107df20 100644 --- a/ballerina-shell/modules/shell-core/src/test/resources/testcases/evaluator/operations.immutable.json +++ b/ballerina-shell/modules/shell-core/src/test/resources/testcases/evaluator/operations.immutable.json @@ -1,11 +1,11 @@ [ { "description": "Define Details.", - "code": "type Details record {| string name; int id; |};" + "code": "type DetailsImmutableTest record {| string name; int id; |};" }, { "description": "Define Student.", - "code": "type Student record {| int 'class; Details details; map marks; |};" + "code": "type StudentImmutableTest record {| int 'class; DetailsImmutableTest details; map marks; |};" }, { "description": "Define addEntryToMap.", @@ -13,11 +13,11 @@ }, { "description": "Define immutable Details", - "code": "Details & readonly immutableDetails = { name: \"May\", id: 112233 };" + "code": "DetailsImmutableTest & readonly immutableDetails = { name: \"May\", id: 112233 };" }, { "description": "Define immutable Student &", - "code": "Student & readonly student = { 'class: 12, details: immutableDetails, marks: { math: 80, physics: 85, chemistry: 75 } };" + "code": "StudentImmutableTest & readonly student = { 'class: 12, details: immutableDetails, marks: { math: 80, physics: 85, chemistry: 75 } };" }, { "description": "Readonly status of student.", 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 9dd918efebe1..7b8b06a95715 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 final 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/creators/TypeCreator.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/creators/TypeCreator.java index b041914300b6..e1a627bbf879 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/creators/TypeCreator.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/creators/TypeCreator.java @@ -50,6 +50,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * Class @{@link TypeCreator} provides APIs to create ballerina type instances. @@ -58,6 +59,7 @@ */ public final class TypeCreator { + private static final RecordTypeCache registeredRecordTypes = new RecordTypeCache(); /** * Creates a new array type with given element type. * @@ -147,7 +149,7 @@ public static TupleType createTupleType(List typeList, Type restType, int * @return the new tuple type */ public static TupleType createTupleType(List typeList, Type restType, - int typeFlags, boolean isCyclic, boolean readonly) { + int typeFlags, boolean isCyclic, boolean readonly) { return new BTupleType(typeList, restType, typeFlags, isCyclic, readonly); } @@ -162,16 +164,16 @@ public static TupleType createTupleType(List typeList, Type restType, * @return the new tuple type */ public static TupleType createTupleType(String name, Module pkg, - int typeFlags, boolean isCyclic, boolean readonly) { + int typeFlags, boolean isCyclic, boolean readonly) { return new BTupleType(name, pkg, typeFlags, isCyclic, readonly); } /** - * Create a {@code MapType} which represents the map type. - * - * @param constraint constraint type which particular map is bound to. - * @return the new map type - */ + * Create a {@code MapType} which represents the map type. + * + * @param constraint constraint type which particular map is bound to. + * @return the new map type + */ public static MapType createMapType(Type constraint) { return new BMapType(constraint); } @@ -224,6 +226,10 @@ public static MapType createMapType(String typeName, Type constraint, Module mod */ public static RecordType createRecordType(String typeName, Module module, long flags, boolean sealed, int typeFlags) { + BRecordType memo = registeredRecordType(typeName, module); + if (memo != null) { + return memo; + } return new BRecordType(typeName, typeName, module, flags, sealed, typeFlags); } @@ -240,8 +246,11 @@ public static RecordType createRecordType(String typeName, Module module, long f * @return the new record type */ public static RecordType createRecordType(String typeName, Module module, long flags, Map fields, - Type restFieldType, - boolean sealed, int typeFlags) { + Type restFieldType, boolean sealed, int typeFlags) { + BRecordType memo = registeredRecordType(typeName, module); + if (memo != null) { + return memo; + } return new BRecordType(typeName, module, flags, fields, restFieldType, sealed, typeFlags); } @@ -520,4 +529,45 @@ public static FiniteType createFiniteType(String typeName, Set values, i private TypeCreator() { } + + private static BRecordType registeredRecordType(String typeName, Module pkg) { + if (typeName == null || pkg == null) { + return null; + } + return registeredRecordTypes.get(new TypeIdentifier(typeName, pkg)); + } + + public static void registerRecordType(BRecordType recordType) { + String name = recordType.getName(); + Module pkg = recordType.getPackage(); + if (name == null || pkg == null) { + return; + } + if (name.contains("$anon")) { + return; + } + TypeIdentifier typeIdentifier = new TypeIdentifier(name, pkg); + registeredRecordTypes.put(typeIdentifier, recordType); + } + + private static final class RecordTypeCache { + + private static final Map cache = new ConcurrentHashMap<>(); + + BRecordType get(TypeIdentifier key) { + return cache.get(key); + } + + void put(TypeIdentifier identifier, BRecordType value) { + cache.put(identifier, value); + } + } + + public record TypeIdentifier(String typeName, Module pkg) { + + public TypeIdentifier { + assert typeName != null; + assert pkg != null; + } + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/PredefinedTypes.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/PredefinedTypes.java index 6541d6aa9811..00cee703124c 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/PredefinedTypes.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/PredefinedTypes.java @@ -64,7 +64,7 @@ */ public final 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/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..0ddea623de6c --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Atom.java @@ -0,0 +1,35 @@ +/* + * 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.11.0 + */ +public sealed interface Atom permits RecAtom, TypeAtom { + + /** + * Get the index of the atom. For {@code TypeAtoms} this is a unique index within the {@code Env}. Each + * {@code RecAtom} that points to the same {@code TypeAtom} will have the same index. + * + * @return index of the 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..7e5163fa8a78 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/AtomicType.java @@ -0,0 +1,33 @@ +/* + * 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.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.FunctionAtomicType; +import io.ballerina.runtime.internal.types.semtype.ListAtomicType; +import io.ballerina.runtime.internal.types.semtype.MappingAtomicType; + +/** + * Marker type representing AtomicType. + * + * @since 2201.11.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..f3fd6f57048b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BasicTypeBitSet.java @@ -0,0 +1,19 @@ +package io.ballerina.runtime.api.types.semtype; + +abstract sealed class BasicTypeBitSet permits SemType { + + private int all; + + protected BasicTypeBitSet(int all) { + this.all = all; + } + + protected void setAll(int all) { + this.all = all; + } + + public final int all() { + assert all != -1 : "SemType created by no arg constructor must be initialized with setAll"; + return all; + } +} 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..c9158bce4bf5 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BasicTypeCode.java @@ -0,0 +1,133 @@ +/* + * 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.11.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_REGEXP = 0x0A; + public static final int CODE_FUTURE = 0x0B; + public static final int CODE_STREAM = 0x0C; + public static final int CODE_LIST = 0x0D; + public static final int CODE_MAPPING = 0x0E; + public static final int CODE_TABLE = 0x0F; + public static final int CODE_XML = 0x10; + public static final int CODE_OBJECT = 0x11; + public static final int CODE_CELL = 0x12; + public static final int CODE_UNDEF = 0x13; + + // Inherently immutable + public static final BasicTypeCode BT_NIL = get(CODE_NIL); + public static final BasicTypeCode BT_BOOLEAN = get(CODE_BOOLEAN); + public static final BasicTypeCode BT_INT = get(CODE_INT); + public static final BasicTypeCode BT_FLOAT = get(CODE_FLOAT); + public static final BasicTypeCode BT_DECIMAL = get(CODE_DECIMAL); + public static final BasicTypeCode BT_STRING = get(CODE_STRING); + public static final BasicTypeCode BT_ERROR = get(CODE_ERROR); + public static final BasicTypeCode BT_TYPEDESC = get(CODE_TYPEDESC); + public static final BasicTypeCode BT_HANDLE = get(CODE_HANDLE); + public static final BasicTypeCode BT_FUNCTION = get(CODE_FUNCTION); + public static final BasicTypeCode BT_REGEXP = get(CODE_REGEXP); + + // Inherently mutable + public static final BasicTypeCode BT_FUTURE = get(CODE_FUTURE); + public static final BasicTypeCode BT_STREAM = get(CODE_STREAM); + + // Selectively immutable + public static final BasicTypeCode BT_LIST = get(CODE_LIST); + public static final BasicTypeCode BT_MAPPING = get(CODE_MAPPING); + public static final BasicTypeCode BT_TABLE = get(CODE_TABLE); + public static final BasicTypeCode BT_XML = get(CODE_XML); + public static final BasicTypeCode BT_OBJECT = get(CODE_OBJECT); + + // Non-val + public static final BasicTypeCode BT_CELL = get(CODE_CELL); + public static final BasicTypeCode BT_UNDEF = get(CODE_UNDEF); + + // Helper bit fields (does not represent basic type tag) + static final int VT_COUNT = CODE_OBJECT + 1; + public static final int BASIC_TYPE_MASK = (1 << (CODE_STRING + 1)) - 1; + public static final int VT_MASK = (1 << VT_COUNT) - 1; + + static final int VT_COUNT_INHERENTLY_IMMUTABLE = CODE_FUTURE; + public static final int VT_INHERENTLY_IMMUTABLE = (1 << VT_COUNT_INHERENTLY_IMMUTABLE) - 1; + + private final int code; + + private BasicTypeCode(int code) { + this.code = code; + } + + public static BasicTypeCode get(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_UNDEF + 2]; + for (int i = CODE_NIL; i < CODE_UNDEF + 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..2573230f05a8 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Bdd.java @@ -0,0 +1,285 @@ +/* + * 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.11.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 BddNodeImpl(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, BddPredicate predicate) { + return bddEvery(cx, b, null, null, predicate); + } + + private 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(); + + // This is for debugging purposes. + // It uses the Frisch/Castagna notation. + public static String bddToString(Bdd b, boolean inner) { + if (b instanceof BddAllOrNothing) { + return b.isAll() ? "1" : "0"; + } else { + String str; + BddNode bdd = (BddNode) b; + Atom a = bdd.atom(); + + if (a instanceof RecAtom) { + str = "r"; + } else { + str = "a"; + } + str += a.index(); + str += "?" + bddToString(bdd.left(), true) + ":" + bddToString(bdd.middle(), true) + + ":" + bddToString(bdd.right(), true); + if (inner) { + str = "(" + str + ")"; + } + return str; + } + } + +} 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..e5b6c67ebad5 --- /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.11.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..d08297a81b06 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddIsEmptyPredicate.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; + +/** + * Predicate to check if a BDD is empty. + * + * @since 2201.11.0 + */ +@FunctionalInterface +public interface BddIsEmptyPredicate { + + /** + * Check if the given BDD is empty. + * + * @param cx Type check context + * @param bdd BDD to check + * @return true if the BDD is empty, false otherwise + */ + boolean apply(Context cx, Bdd bdd); +} 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..001f154701ee --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNode.java @@ -0,0 +1,66 @@ +package io.ballerina.runtime.api.types.semtype; + +/** + * Internal node of a BDD, which represents a disjunction of conjunctions of atoms. + * + * @since 2201.11.0 + */ +public abstract sealed class BddNode extends Bdd permits BddNodeImpl, BddNodeSimple { + + private volatile Integer hashCode = null; + + protected BddNode(boolean all, boolean nothing) { + super(all, nothing); + } + + public static BddNode bddAtom(Atom atom) { + return new BddNodeSimple(atom); + } + + public boolean isSimple() { + return this instanceof BddNodeSimple; + } + + public abstract Atom atom(); + + public abstract Bdd left(); + + public abstract Bdd middle(); + + public abstract Bdd 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) { + // No need to synchronize this since {@code computeHashCode} is idempotent + 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; + } + + @Override + public boolean posMaybeEmpty() { + return middle().posMaybeEmpty() || right().posMaybeEmpty(); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNodeImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNodeImpl.java new file mode 100644 index 000000000000..71b1a9528220 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNodeImpl.java @@ -0,0 +1,60 @@ +/* + * 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; + +/** + * Generalized implementation of {@code BddNode}. + * + * @since 2201.11.0 + */ +final class BddNodeImpl extends BddNode { + + private final Atom atom; + private final Bdd left; + private final Bdd middle; + private final Bdd right; + + BddNodeImpl(Atom atom, Bdd left, Bdd middle, Bdd right) { + super(false, false); + this.atom = atom; + this.left = left; + this.middle = middle; + this.right = right; + } + + @Override + public Atom atom() { + return atom; + } + + @Override + public Bdd left() { + return left; + } + + @Override + public Bdd middle() { + return middle; + } + + @Override + public Bdd right() { + return right; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNodeSimple.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNodeSimple.java new file mode 100644 index 000000000000..bd64cdcf9af5 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/BddNodeSimple.java @@ -0,0 +1,37 @@ +package io.ballerina.runtime.api.types.semtype; + +/** + * Represent a Bdd node that contains a single atom as positive. This is used to reduce the memory overhead of + * BddNodeImpl in representing such nodes + * + * @since 2201.11.0 + */ +final class BddNodeSimple extends BddNode { + + private final Atom atom; + + BddNodeSimple(Atom atom) { + super(false, false); + this.atom = atom; + } + + @Override + public Atom atom() { + return atom; + } + + @Override + public Bdd left() { + return BddAllOrNothing.ALL; + } + + @Override + public Bdd middle() { + return BddAllOrNothing.NOTHING; + } + + @Override + public Bdd right() { + return BddAllOrNothing.NOTHING; + } +} 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..4a6e47f3c870 --- /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.11.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..4c4f1149387b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Builder.java @@ -0,0 +1,456 @@ +/* + * 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.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.BStringSubType; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.FixedLengthArray; +import io.ballerina.runtime.internal.types.semtype.ListAtomicType; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; +import io.ballerina.runtime.internal.types.semtype.MappingAtomicType; +import io.ballerina.runtime.internal.types.semtype.MappingDefinition; +import io.ballerina.runtime.internal.types.semtype.TableUtils; +import io.ballerina.runtime.internal.types.semtype.XmlUtils; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +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_FUTURE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_HANDLE; +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.BT_REGEXP; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_TYPEDESC; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_XML; +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.BddNode.bddAtom; +import static io.ballerina.runtime.api.types.semtype.Core.union; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_LIMITED; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; + +/** + * Utility class for creating semtypes. + * + * @since 2201.11.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 = getBasicTypeUnion(VAL.all() | from(BasicTypeCode.BT_UNDEF).all()); + private static final SemType ANY = getBasicTypeUnion(BasicTypeCode.VT_MASK & ~(1 << BasicTypeCode.BT_ERROR.code())); + private static final SemType SIMPLE_OR_STRING = + getBasicTypeUnion((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 << BT_REGEXP.code()) + | (1 << BasicTypeCode.BT_STRING.code())); + + private static final SemType[] EMPTY_TYPES_ARR = new SemType[0]; + + private static final ConcurrentLazySupplier MAPPING_RO = new ConcurrentLazySupplier<>(() -> + basicSubType(BT_MAPPING, BMappingSubType.createDelegate(getBddSubtypeRo())) + ); + private static final ConcurrentLazySupplier ANYDATA = new ConcurrentLazySupplier<>( + () -> { + Env env = Env.getInstance(); + ListDefinition listDef = new ListDefinition(); + MappingDefinition mapDef = new MappingDefinition(); + SemType tableTy = TableUtils.tableContaining(env, mapDef.getSemType(env)); + SemType accum = Stream.of(getSimpleOrStringType(), getXmlType(), listDef.getSemType(env), + mapDef.getSemType(env), + tableTy).reduce(Builder.getNeverType(), Core::union); + listDef.defineListTypeWrapped(env, EMPTY_TYPES_ARR, 0, accum, CELL_MUT_LIMITED); + mapDef.defineMappingTypeWrapped(env, new MappingDefinition.Field[0], accum, CELL_MUT_LIMITED); + return accum; + } + ); + private static final ConcurrentLazySupplier INNER_RO = + new ConcurrentLazySupplier<>(() -> union(getReadonlyType(), getInnerType())); + + private static final ConcurrentLazySupplier LIST_ATOMIC_INNER = + new ConcurrentLazySupplier<>(() -> new ListAtomicType( + FixedLengthArray.empty(), PredefinedTypeEnv.getInstance().cellSemTypeInner())); + private static final ConcurrentLazySupplier MAPPING_ATOMIC_INNER = + new ConcurrentLazySupplier<>(() -> new MappingAtomicType( + EMPTY_STRING_ARR, EMPTY_TYPES_ARR, PredefinedTypeEnv.getInstance().cellSemTypeInner())); + + private static final ConcurrentLazySupplier XML_ELEMENT = new ConcurrentLazySupplier<>(() -> + XmlUtils.xmlSingleton(XmlUtils.XML_PRIMITIVE_ELEMENT_RO | XmlUtils.XML_PRIMITIVE_ELEMENT_RW)); + private static final ConcurrentLazySupplier XML_COMMENT = new ConcurrentLazySupplier<>(() -> + XmlUtils.xmlSingleton(XmlUtils.XML_PRIMITIVE_COMMENT_RO | XmlUtils.XML_PRIMITIVE_COMMENT_RW)); + private static final ConcurrentLazySupplier XML_TEXT = new ConcurrentLazySupplier<>(() -> + XmlUtils.xmlSingleton(XmlUtils.XML_PRIMITIVE_TEXT)); + private static final ConcurrentLazySupplier XML_PI = new ConcurrentLazySupplier<>(() -> + XmlUtils.xmlSingleton(XmlUtils.XML_PRIMITIVE_PI_RO | XmlUtils.XML_PRIMITIVE_PI_RW)); + private static final ConcurrentLazySupplier XML_NEVER = new ConcurrentLazySupplier<>(() -> + XmlUtils.xmlSingleton(XmlUtils.XML_PRIMITIVE_NEVER)); + private static final PredefinedTypeEnv PREDEFINED_TYPE_ENV = PredefinedTypeEnv.getInstance(); + private static final BddNode LIST_SUBTYPE_THREE_ELEMENT = bddAtom(PREDEFINED_TYPE_ENV.atomListThreeElement()); + private static final BddNode LIST_SUBTYPE_THREE_ELEMENT_RO = bddAtom(PREDEFINED_TYPE_ENV.atomListThreeElementRO()); + private static final BddNode LIST_SUBTYPE_TWO_ELEMENT = bddAtom(PREDEFINED_TYPE_ENV.atomListTwoElement()); + + 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 getNeverType() { + return SemType.from(0); + } + + public static SemType getNilType() { + return from(BasicTypeCode.BT_NIL); + } + + public static SemType getUndefType() { + return from(BasicTypeCode.BT_UNDEF); + } + + public static SemType getCellType() { + return from(BT_CELL); + } + + public static SemType getInnerType() { + return INNER; + } + + public static SemType getIntType() { + return from(BasicTypeCode.BT_INT); + } + + public static SemType getDecimalType() { + return from(BasicTypeCode.BT_DECIMAL); + } + + public static SemType getFloatType() { + return from(BasicTypeCode.BT_FLOAT); + } + + public static SemType getBooleanType() { + return from(BasicTypeCode.BT_BOOLEAN); + } + + public static SemType getStringType() { + return from(BasicTypeCode.BT_STRING); + } + + public static SemType getCharType() { + return StringTypeCache.charType; + } + + public static SemType getListType() { + return from(BT_LIST); + } + + public static SemType getReadonlyType() { + return PREDEFINED_TYPE_ENV.readonlyType(); + } + + static SemType getBasicTypeUnion(int bitset) { + return switch (bitset) { + case 0 -> getNeverType(); + case VT_MASK -> VAL; + default -> { + if (Integer.bitCount(bitset) == 1) { + int code = Integer.numberOfTrailingZeros(bitset); + // TODO: what are the others? + 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) : "BDd is wrapped in wrong delegate"; + 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 getIntConst(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 getBooleanConst(boolean value) { + return value ? BooleanTypeCache.TRUE : BooleanTypeCache.FALSE; + } + + public static SemType createIntRange(long min, long max) { + return basicSubType(BasicTypeCode.BT_INT, BIntSubType.createIntSubType(min, max)); + } + + public static SemType getDecimalConst(BigDecimal value) { + BigDecimal[] values = {value}; + return basicSubType(BasicTypeCode.BT_DECIMAL, BDecimalSubType.createDecimalSubType(true, values)); + } + + public static SemType getFloatConst(double value) { + Double[] values = {value}; + return basicSubType(BasicTypeCode.BT_FLOAT, BFloatSubType.createFloatSubType(true, values)); + } + + // TODO: consider caching small strings + public static SemType getStringConst(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 SemType getRoCellContaining(Env env, SemType ty) { + return getCellContaining(env, ty, CELL_MUT_NONE); + } + + public static SemType getRwCellContaining(Env env, SemType ty) { + return getCellContaining(env, ty, CellAtomicType.CellMutability.CELL_MUT_LIMITED); + } + + public static SemType getCellContaining(Env env, SemType ty, CellAtomicType.CellMutability mut) { + return env.getCachedCellType(ty, mut, () -> createCellSemType(env, ty, mut)); + } + + private static SemType createCellSemType(Env env, SemType ty, CellAtomicType.CellMutability mut) { + CellAtomicType atomicCell = CellAtomicType.from(ty, mut); + TypeAtom atom = env.cellAtom(atomicCell); + BddNode bdd = bddAtom(atom); + return basicSubType(BT_CELL, BCellSubType.createDelegate(bdd)); + } + + public static SemType getValType() { + return getBasicTypeUnion(VT_MASK); + } + + public static SemType getAnyType() { + return ANY; + } + + public static SemType getMappingType() { + return from(BT_MAPPING); + } + + public static SemType getFunctionType() { + return from(BT_FUNCTION); + } + + public static SemType getErrorType() { + return from(BT_ERROR); + } + + public static SemType getXmlType() { + return from(BT_XML); + } + + public static SemType getXmlElementType() { + return XML_ELEMENT.get(); + } + + public static SemType getXmlCommentType() { + return XML_COMMENT.get(); + } + + public static SemType getXmlTextType() { + return XML_TEXT.get(); + } + + public static SemType getXmlNeverType() { + return XML_NEVER.get(); + } + + public static SemType getXmlPIType() { + return XML_PI.get(); + } + + public static SemType getHandleType() { + return from(BT_HANDLE); + } + + public static SemType getFutureType() { + return from(BT_FUTURE); + } + + public static SemType getRegexType() { + return from(BT_REGEXP); + } + + public static SemType getTypeDescType() { + return from(BT_TYPEDESC); + } + + public static SemType getStreamType() { + return from(BasicTypeCode.BT_STREAM); + } + + public static SemType getAnyDataType() { + return ANYDATA.get(); + } + + public static SemType getObjectType() { + 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 getBddSubtypeRo() { + return bddAtom(RecAtom.createRecAtom(0)); + } + + public static ListAtomicType getListAtomicInner() { + return LIST_ATOMIC_INNER.get(); + } + + public static MappingAtomicType getMappingAtomicInner() { + return MAPPING_ATOMIC_INNER.get(); + } + + public static BddNode getListSubtypeThreeElement() { + return LIST_SUBTYPE_THREE_ELEMENT; + } + + public static BddNode getListSubtypeThreeElementRO() { + return LIST_SUBTYPE_THREE_ELEMENT_RO; + } + + public static BddNode getListSubtypeTwoElement() { + return LIST_SUBTYPE_TWO_ELEMENT; + } + + public static SemType getSimpleOrStringType() { + return SIMPLE_OR_STRING; + } + + public static SemType getTableType() { + return from(BasicTypeCode.BT_TABLE); + } + + 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_UNDEF + 2]; + for (int i = 0; i < CODE_UNDEF + 1; i++) { + cache[i] = SemType.from(1 << i); + } + } + + private static boolean isCached(BasicTypeCode code) { + int i = code.code(); + return 0 < i && i <= CODE_UNDEF; + } + + private static boolean isCached(int code) { + return 0 < code && code <= CODE_UNDEF; + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/CacheableTypeDescriptor.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/CacheableTypeDescriptor.java new file mode 100644 index 000000000000..bad069886c65 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/CacheableTypeDescriptor.java @@ -0,0 +1,41 @@ +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.api.types.Type; + +import java.util.Optional; + +/** + * Represent TypeDescriptors whose type check results can be cached. + * + * @since 2201.11.0 + */ +public interface CacheableTypeDescriptor extends Type { + + /** + * Check whether the type check result of this type descriptor should be cached. Can be used to avoid caching in + * cases where either directly doing the type check is cheaper or we can't determine if two instances of a type + * descriptor are equal without doing a type check. + * + * @return true if the type check result should be cached, false otherwise + */ + boolean shouldCache(); + + /** + * Check whether the type check result of this type descriptor is cached for the given type descriptor. + * + * @param cx Context in which the type check is performed + * @param other Type descriptor to check the cached result for + * @return Optional containing the cached result if it is cached, empty otherwise + */ + Optional cachedTypeCheckResult(Context cx, CacheableTypeDescriptor other); + + /** + * Cache the type check result of this type descriptor for the given type descriptor. Note that implementations of + * this method could choose to not cache the result if {@link #shouldCache()} returns false. In such cases, even + * after calling this method, {@link #cachedTypeCheckResult(Context, CacheableTypeDescriptor)} could return empty. + * + * @param other Type descriptor to cache the result for + * @param result Result of the type check + */ + void cacheTypeCheckResult(CacheableTypeDescriptor other, boolean result); +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ConcurrentLazySupplier.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ConcurrentLazySupplier.java new file mode 100644 index 000000000000..2ea49c4906f1 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ConcurrentLazySupplier.java @@ -0,0 +1,36 @@ +package io.ballerina.runtime.api.types.semtype; + +import java.util.function.Supplier; + +/** + * A thread-safe single lazy supplier that initialize the value only once. + * + * @param type of the value + * @since 2201.11.0 + */ +public class ConcurrentLazySupplier implements Supplier { + + private Supplier initializer; + private volatile E value = null; + + public ConcurrentLazySupplier(Supplier initializer) { + this.initializer = initializer; + } + + @Override + public E get() { + E result = value; + if (result == null) { + synchronized (this) { + result = value; + if (result == null) { + result = initializer.get(); + assert result != null; + value = result; + initializer = null; + } + } + } + return result; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ConcurrentLazySupplierWithCallback.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ConcurrentLazySupplierWithCallback.java new file mode 100644 index 000000000000..94646141bb5b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ConcurrentLazySupplierWithCallback.java @@ -0,0 +1,59 @@ +/* + * 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.function.Consumer; +import java.util.function.Supplier; + +/** + * A thread-safe lazy supplier that initializes the value on the first call to {@link #get()} and calls the callback. + * + * @param the type of the value + * @since 2201.11.0 + */ +public class ConcurrentLazySupplierWithCallback implements Supplier { + + private volatile E value = null; + private Supplier initializer; + private Consumer callback; + + ConcurrentLazySupplierWithCallback(Supplier initializer, Consumer callback) { + this.initializer = initializer; + this.callback = callback; + } + + @Override + public E get() { + E result = value; + if (result == null) { + synchronized (this) { + result = value; + if (result == null) { + result = initializer.get(); + assert result != null; + value = result; + initializer = null; + callback.accept(result); + callback = null; + } + } + } + return result; + } +} 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..2faf2c559857 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Conjunction.java @@ -0,0 +1,35 @@ +/* + * 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.11.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..4d6634f89f76 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Context.java @@ -0,0 +1,159 @@ +/* + * 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.BddMemo; +import io.ballerina.runtime.internal.types.semtype.FunctionAtomicType; +import io.ballerina.runtime.internal.types.semtype.ListAtomicType; +import io.ballerina.runtime.internal.types.semtype.MappingAtomicType; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.WeakHashMap; + +/** + * Context in which type checking operations are performed. Note context is not + * thread safe, and multiple type check operations should not use the same + * context concurrently. Multiple contexts may share same environment without + * issue. + * + * @since 2201.11.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 WeakHashMap<>(); + public final Map mappingMemo = new WeakHashMap<>(); + public final Map functionMemo = new WeakHashMap<>(); + private static final int MAX_CACHE_SIZE = 100; + private final Map> typeCheckCacheMemo; + + private Context(Env env) { + this.env = env; + this.typeCheckCacheMemo = createTypeCheckCacheMemo(); + } + + private static Map> createTypeCheckCacheMemo() { + // This is fine since this map is not going to get leaked out of the context and + // context is unique to a thread. So there will be no concurrent modifications + return new LinkedHashMap<>(MAX_CACHE_SIZE, 1f, true) { + @Override + protected boolean removeEldestEntry( + Map.Entry> eldest) { + return size() > MAX_CACHE_SIZE; + } + }; + } + + public static Context from(Env env) { + return new Context(env); + } + + /** + * Memoization logic + * Castagna's paper does not deal with this fully. Although he calls it memoization, it is not, strictly speaking, + * just memoization, since it is not just an optimization, but required for correct handling of recursive types. + * The handling of recursive types depends on our types being defined inductively, rather than coinductively. + * This means that each shape that is a member of the set denoted by the type is finite. There is a tricky problem + * here with memoizing results that rely on assumptions that subsequently turn out to be false. Memoization/caching + * is discussed in section 7.1.2 of the Frisch thesis. This follows Frisch's approach of undoing memoizations that + * turn out to be wrong. (I did not succeed in fully understanding his approach, so I am not completely sure if we + * are doing the same.) + * @param memoTable corresponding memo table for the Bdd + * @param isEmptyPredicate predicate to be applied on the Bdd + * @param bdd Bdd to be checked + * @return result of applying predicate on the bdd + */ + public boolean memoSubtypeIsEmpty(Map memoTable, BddIsEmptyPredicate isEmptyPredicate, Bdd bdd) { + BddMemo m = memoTable.computeIfAbsent(bdd, ignored -> new BddMemo()); + return m.isEmpty().orElseGet(() -> memoSubTypeIsEmptyInner(isEmptyPredicate, bdd, m)); + } + + private boolean memoSubTypeIsEmptyInner(BddIsEmptyPredicate isEmptyPredicate, Bdd bdd, BddMemo m) { + // We are staring the type check with the assumption our type is empty (see: inductive type) + 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 not empty our assumption is wrong so we need to reset the memoized values, otherwise we cleanup the stack + // at the end + if (!isEmpty || initStackDepth == 0) { + resetMemoizedValues(initStackDepth, isEmpty, isLoop, m); + } + return isEmpty; + } + + private void resetMemoizedValues(int initStackDepth, boolean isEmpty, boolean isLoop, BddMemo m) { + 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) { + // We started with the assumption our type is empty. Now we know for sure if we are empty or not + // if we are empty all of these who don't have anything except us should be empty as well. + // Otherwise, we don't know if they are empty or not + memoStack.get(i).isEmpty = isEmpty ? BddMemo.Status.TRUE : BddMemo.Status.NULL; + } + } + if (memoStack.size() > initStackDepth) { + memoStack.subList(initStackDepth, memoStack.size()).clear(); + } + if (isLoop && isEmpty) { + // 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. + m.isEmpty = BddMemo.Status.CYCLIC; + } else { + m.isEmpty = isEmpty ? BddMemo.Status.TRUE : BddMemo.Status.FALSE; + } + } + + 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 TypeCheckCache getTypeCheckCache(CacheableTypeDescriptor typeDescriptor) { + return typeCheckCacheMemo.computeIfAbsent(typeDescriptor, TypeCheckCache::new); + } +} 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..3eefea1b58e4 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Core.java @@ -0,0 +1,520 @@ +/* + * 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.BFutureSubType; +import io.ballerina.runtime.internal.types.semtype.BIntSubType; +import io.ballerina.runtime.internal.types.semtype.BObjectSubType; +import io.ballerina.runtime.internal.types.semtype.BStreamSubType; +import io.ballerina.runtime.internal.types.semtype.BTableSubType; +import io.ballerina.runtime.internal.types.semtype.BTypedescSubType; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.DelegatedSubType; +import io.ballerina.runtime.internal.types.semtype.EnumerableSubtypeData; +import io.ballerina.runtime.internal.types.semtype.ListAtomicType; +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.math.BigDecimal; +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Function; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_CELL; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_DECIMAL; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_FLOAT; +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_FUTURE; +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_TABLE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_TYPEDESC; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.VT_MASK; +import static io.ballerina.runtime.api.types.semtype.Builder.getListType; +import static io.ballerina.runtime.api.types.semtype.Builder.getUndefType; +import static io.ballerina.runtime.internal.types.semtype.BListSubType.bddListMemberTypeInnerVal; +import static io.ballerina.runtime.internal.types.semtype.BMappingProj.mappingMemberTypeInner; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.cellAtomType; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.intersectCellAtomicType; + +/** + * Contain functions defined in `core.bal` file. + * + * @since 2201.11.0 + */ +public final class Core { + + 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.getBasicTypeUnion(all1 & ~all2); + } else { + if (all1 == 0) { + return t1; + } + } + } else { + if (some2 == 0) { + if (all2 == VT_MASK) { + return Builder.getBasicTypeUnion(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(some, subtypes) : subtypes); + } + + // TODO: this should return SubTypeData not subtype + 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() & getListType().all()) != 0 ? Builder.getValType() : Builder.getNeverType(); + } else { + SubTypeData keyData = intSubtype(k); + if (isNothingSubtype(keyData)) { + return Builder.getNeverType(); + } + return bddListMemberTypeInnerVal(cx, (Bdd) getComplexSubtypeData(t, BT_LIST), keyData, + Builder.getValType()); + } + } + + 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.getBasicTypeUnion(all1 | all2); + } + } + + int all = all1 | all2; + int some = (some1 | some2) & ~all; + if (some == 0) { + return Builder.getBasicTypeUnion(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(some, subtypes) : subtypes); + } + + private static SubType[] filterNulls(int some, SubType[] subtypes) { + int newSize = cardinality(some); + SubType[] filtered = new SubType[newSize]; + System.arraycopy(subtypes, 0, filtered, 0, newSize); + return filtered; + } + + 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; + i++; + } else { + some &= ~(1 << code); + filterNulls = true; + } + } + if (some == 0) { + return SemType.from(all); + } + return SemType.from(all, some, filterNulls ? filterNulls(some, 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.getValType(), t); + } + + public static boolean isNever(SemType t) { + return t.all() == 0 && t.some() == 0; + } + + public static boolean isSubType(Context cx, SemType t1, SemType t2) { + return isEmpty(cx, diff(t1, t2)); + } + + 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; + } + + public static SubTypeData intSubtype(SemType t) { + return subTypeData(t, BT_INT); + } + + 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); + } + + private static int cardinality(int bitset) { + return Integer.bitCount(bitset); + } + + public static SemType getCellContainingInnerVal(Env env, SemType t) { + CellAtomicType cat = + cellAtomicType(t).orElseThrow(() -> new IllegalArgumentException("t is not a cell semtype")); + return Builder.getCellContaining(env, diff(cat.ty(), getUndefType()), cat.mut()); + } + + public static SemType intersectCellMemberSemTypes(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 Builder.getCellContaining(env, atomicType.ty(), + getUndefType().equals(atomicType.ty()) ? CELL_MUT_NONE : atomicType.mut()); + } + + public static Optional cellAtomicType(SemType t) { + SemType cell = Builder.getCellType(); + 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), getUndefType()); + } + + 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.getNeverType(); + } + SubType subType = switch (typeCode.code()) { + case CODE_OBJECT -> BObjectSubType.createDelegate(bdd); + case CODE_FUTURE -> BFutureSubType.createDelegate(bdd); + case CODE_TYPEDESC -> BTypedescSubType.createDelegate(bdd); + case CODE_TABLE -> BTableSubType.createDelegate(bdd); + case CODE_STREAM -> BStreamSubType.createDelegate(bdd); + default -> throw new IllegalArgumentException("Unexpected type code: " + typeCode); + }; + return SemType.from(0, 1 << typeCode.code(), new SubType[]{subType}); + } + + public static SemType mappingMemberTypeInnerVal(Context cx, SemType t, SemType k) { + return diff(mappingMemberTypeInner(cx, t, k), Builder.getUndefType()); + } + + public static Optional listAtomicType(Context cx, SemType t) { + ListAtomicType listAtomicInner = Builder.getListAtomicInner(); + if (t.some() == 0) { + return Core.isSubtypeSimple(t, Builder.getListType()) ? Optional.ofNullable(listAtomicInner) : + Optional.empty(); + } + Env env = cx.env; + if (!isSubtypeSimple(t, Builder.getListType())) { + return Optional.empty(); + } + return bddListAtomicType(env, (Bdd) getComplexSubtypeData(t, BT_LIST), listAtomicInner); + } + + public static SemType floatToInt(SemType t) { + if (!containsBasicType(t, Builder.getFloatType())) { + return Builder.getNeverType(); + } + return convertEnumerableNumericType(t, BT_FLOAT, Builder.getIntType(), + (floatValue) -> ((Double) floatValue).longValue(), Builder::getIntConst); + } + + public static SemType floatToDecimal(SemType t) { + if (!containsBasicType(t, Builder.getFloatType())) { + return Builder.getNeverType(); + } + return convertEnumerableNumericType(t, BT_FLOAT, Builder.getDecimalType(), + (floatValue) -> BigDecimal.valueOf((Double) floatValue), Builder::getDecimalConst); + } + + public static SemType decimalToInt(SemType t) { + if (!containsBasicType(t, Builder.getDecimalType())) { + return Builder.getNeverType(); + } + return convertEnumerableNumericType(t, BT_DECIMAL, Builder.getIntType(), + (decimalVal) -> ((BigDecimal) decimalVal).longValue(), Builder::getIntConst); + } + + public static SemType decimalToFloat(SemType t) { + if (!containsBasicType(t, Builder.getDecimalType())) { + return Builder.getNeverType(); + } + return convertEnumerableNumericType(t, BT_DECIMAL, Builder.getFloatType(), + (decimalVal) -> ((BigDecimal) decimalVal).doubleValue(), Builder::getFloatConst); + } + + public static SemType intToFloat(SemType t) { + if (!containsBasicType(t, Builder.getIntType())) { + return Builder.getNeverType(); + } + SubTypeData subTypeData = subTypeData(t, BT_INT); + if (subTypeData == AllOrNothing.NOTHING) { + return Builder.getNeverType(); + } + if (subTypeData == AllOrNothing.ALL) { + return Builder.getFloatType(); + } + BIntSubType.IntSubTypeData intSubTypeData = (BIntSubType.IntSubTypeData) subTypeData; + return intSubTypeData.values().stream().map(Builder::getFloatConst).reduce(Builder.getNeverType(), Core::union); + } + + public static SemType intToDecimal(SemType t) { + if (!containsBasicType(t, Builder.getIntType())) { + return Builder.getNeverType(); + } + SubTypeData subTypeData = subTypeData(t, BT_INT); + if (subTypeData == AllOrNothing.NOTHING) { + return Builder.getNeverType(); + } + if (subTypeData == AllOrNothing.ALL) { + return Builder.getDecimalType(); + } + BIntSubType.IntSubTypeData intSubTypeData = (BIntSubType.IntSubTypeData) subTypeData; + return intSubTypeData.values().stream().map(BigDecimal::new).map(Builder::getDecimalConst) + .reduce(Builder.getNeverType(), Core::union); + } + + private static , T extends Comparable> SemType convertEnumerableNumericType( + SemType source, BasicTypeCode targetTypeCode, SemType topType, Function valueConverter, + Function semTypeCreator) { + SubTypeData subTypeData = subTypeData(source, targetTypeCode); + if (subTypeData == AllOrNothing.NOTHING) { + return Builder.getNeverType(); + } + if (subTypeData == AllOrNothing.ALL) { + return topType; + } + //noinspection unchecked - it's a enumerable type + EnumerableSubtypeData enumerableSubtypeData = (EnumerableSubtypeData) subTypeData; + SemType posType = + Arrays.stream(enumerableSubtypeData.values()).map(valueConverter).distinct().map(semTypeCreator) + .reduce(Builder.getNeverType(), Core::union); + if (enumerableSubtypeData.allowed()) { + return posType; + } + return diff(topType, posType); + } + + private static Optional bddListAtomicType(Env env, Bdd bdd, ListAtomicType top) { + if (!(bdd instanceof BddNode bddNode)) { + if (bdd.isAll()) { + return Optional.ofNullable(top); + } else { + return Optional.empty(); + } + } + return bddNode.isSimple() ? Optional.of(env.listAtomType(bddNode.atom())) : Optional.empty(); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Definition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Definition.java new file mode 100644 index 000000000000..0b0017f05841 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Definition.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; + +import io.ballerina.runtime.internal.types.semtype.DefinitionContainer; + +/** + * Represent a type definition which will act as a layer of indirection between {@code Env} and the type descriptor. + * + * @since 2201.11.0 + */ +public abstract class Definition { + + private DefinitionContainer container; + + /** + * Get the {@code SemType} of this definition in the given environment. + * + * @param env type environment + */ + public abstract SemType getSemType(Env env); + + /** + * Register the container as the holder of this definition. Used to maintain concurrency invariants. + * + * @param container holder of the definition + * @see io.ballerina.runtime.internal.types.semtype.DefinitionContainer + */ + public void registerContainer(DefinitionContainer container) { + this.container = container; + } + + protected void notifyContainer() { + if (container != null) { + container.definitionUpdated(); + } + } +} 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..10b00639a035 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Env.java @@ -0,0 +1,260 @@ +/* + * 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.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.FunctionAtomicType; +import io.ballerina.runtime.internal.types.semtype.ListAtomicType; +import io.ballerina.runtime.internal.types.semtype.MappingAtomicType; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; + +/** + * 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.11.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 preventing multiple instances of Env. + + private static final Env INSTANCE = new Env(); + + // Each atom is created once but will be accessed multiple times during type checking. Also in perfect world we + // will create atoms at the beginning of the execution and will eventually reach + // a steady state. + private final ReadWriteLock atomLock = new ReentrantReadWriteLock(); + private final Map> atomTable; + + 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 WeakHashMap<>(); + 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) { + atomLock.readLock().lock(); + try { + Reference ref = this.atomTable.get(atomicType); + if (ref != null) { + TypeAtom atom = ref.get(); + if (atom != null) { + return atom; + } + } + } finally { + atomLock.readLock().unlock(); + } + atomLock.writeLock().lock(); + try { + TypeAtom result = TypeAtom.createTypeAtom(this.atomTable.size(), atomicType); + this.atomTable.put(result.atomicType(), new WeakReference<>(result)); + return result; + } finally { + atomLock.writeLock().unlock(); + } + } + + // Ideally this cache should be in the builder as well. But technically we can't cache cells across environments. + // In practice this shouldn't be an issue since there should be only one environment, but I am doing this here + // just in case. + SemType getCachedCellType(SemType ty, CellAtomicType.CellMutability mut, Supplier semTypeCreator) { + if (ty.some() != 0) { + return semTypeCreator.get(); + } + return this.cellTypeCache.computeIfAbsent(new CellSemTypeCacheKey(ty, mut), k -> semTypeCreator.get()); + } + + 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) { + // NOTE: this is fine since we are not actually changing the recList + recListLock.readLock().lock(); + try { + this.recListAtoms.set(rec.index(), atomicType); + } finally { + recListLock.readLock().unlock(); + } + + } + + 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 ListAtomicType listAtomType(Atom atom) { + if (atom instanceof RecAtom recAtom) { + return getRecListAtomType(recAtom); + } else { + return (ListAtomicType) ((TypeAtom) atom).atomicType(); + } + } + + 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.readLock().lock(); + try { + this.recMappingAtoms.set(rec.index(), atomicType); + } finally { + recMapLock.readLock().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.readLock().lock(); + try { + this.recFunctionAtoms.set(rec.index(), atomicType); + } finally { + recFunctionLock.readLock().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(); + } + + // This is for debug purposes + public Optional atomicTypeByIndex(int index) { + atomLock.readLock().lock(); + try { + for (Map.Entry> entry : this.atomTable.entrySet()) { + TypeAtom typeAtom = entry.getValue().get(); + if (typeAtom == null) { + continue; + } + if (typeAtom.index() == index) { + return Optional.of(entry.getKey()); + } + } + return Optional.empty(); + } finally { + atomLock.readLock().unlock(); + } + } +} 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..29ce90a72be5 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FieldPair.java @@ -0,0 +1,34 @@ +/* + * 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 matching fields types of two mapping atomic types. + * + * @param name name of the field + * @param type1 type of the field in the first mapping + * @param type2 type of the field in teh second mapping + * @param index1 corresponding index of the field in the first mapping. If matching field is rest value is {@code null} + * @param index2 corresponding index of the field in the second mapping. If matching field is rest value is + * {@code null} + * @since 2201.11.0 + */ +public record FieldPair(String name, SemType type1, SemType type2, Integer index1, Integer 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..82618c4c93f6 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/FieldPairs.java @@ -0,0 +1,171 @@ +/* + * 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 io.ballerina.runtime.internal.types.semtype.MappingAtomicType; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; + +/** + * {@code Iterable} over the matching fields of two mapping atomic types. + * + * @since 2201.11.0 + */ +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 = new FieldPair(curName2(), this.rest1, curType2(), null, this.i2); + this.i2 += 1; + } else if (this.i2 >= this.len2) { + p = new FieldPair(curName1(), curType1(), this.rest2, this.i1, null); + this.i1 += 1; + } else { + String name1 = curName1(); + String name2 = curName2(); + if (Common.codePointCompare(name1, name2)) { + p = new FieldPair(name1, curType1(), this.rest2, this.i1, null); + this.i1 += 1; + } else if (Common.codePointCompare(name2, name1)) { + p = new FieldPair(name2, this.rest1, curType2(), null, this.i2); + this.i2 += 1; + } else { + p = new FieldPair(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/ListProj.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ListProj.java new file mode 100644 index 000000000000..e1d579e9b266 --- /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; + +/** + * Utility class for list type projection. + * + * @since 2201.11.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/MappingProj.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/MappingProj.java new file mode 100644 index 000000000000..cd3c113054d3 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/MappingProj.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * Utility class for mapping type projection. + * + * @since 2201.11.0 + */ +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..f8d4922c7f56 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Pair.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * Data structure used to pass around pairs of values. + * + * @param type of first value + * @param type of second value + * @param first first values + * @param second second value + * @since 2201.11.0 + */ +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..1545a36e572e --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/PredefinedTypeEnv.java @@ -0,0 +1,609 @@ +/* + * 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.BListSubType; +import io.ballerina.runtime.internal.types.semtype.BMappingSubType; +import io.ballerina.runtime.internal.types.semtype.BObjectSubType; +import io.ballerina.runtime.internal.types.semtype.BTableSubType; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.FixedLengthArray; +import io.ballerina.runtime.internal.types.semtype.ListAtomicType; +import io.ballerina.runtime.internal.types.semtype.MappingAtomicType; +import io.ballerina.runtime.internal.types.semtype.XmlUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_CELL; +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.BT_TABLE; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_XML; +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.Builder.basicSubType; +import static io.ballerina.runtime.api.types.semtype.Builder.from; +import static io.ballerina.runtime.api.types.semtype.Builder.getBasicTypeUnion; +import static io.ballerina.runtime.api.types.semtype.Builder.getStringConst; +import static io.ballerina.runtime.api.types.semtype.Core.union; +import static io.ballerina.runtime.api.types.semtype.TypeAtom.createTypeAtom; + +final class PredefinedTypeEnv { + + private static PredefinedTypeEnv instance; + private final AtomicBoolean initialized = new AtomicBoolean(false); + 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); + private static final BddNode MAPPING_SUBTYPE_OBJECT_RO = bddAtom(OBJECT_RO_REC_ATOM); + + 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 final Supplier cellAtomicVal = new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from(getBasicTypeUnion(VT_MASK), CellAtomicType.CellMutability.CELL_MUT_LIMITED), + this::addInitializedCellAtom + ); + private final Supplier atomCellVal = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicVal, this::cellAtomIndex); + private final Supplier cellSemTypeVal = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellVal())))); + private final Supplier cellAtomicNever = new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from(SemType.from(0), CellAtomicType.CellMutability.CELL_MUT_LIMITED), + this::addInitializedCellAtom + ); + private final Supplier atomCellNever = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicNever, this::cellAtomIndex); + // Represent the typeAtom required to construct equivalent subtypes of map and (any|error)[]. + + private final ConcurrentLazySupplier inner = + new ConcurrentLazySupplier<>(() -> SemType.from(VT_MASK | from(BasicTypeCode.BT_UNDEF).all())); + private final Supplier cellAtomicInner = new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from(inner.get(), CellAtomicType.CellMutability.CELL_MUT_LIMITED), + this::addInitializedCellAtom + ); + private final Supplier atomCellInner = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicInner, this::cellAtomIndex); + private final Supplier cellSemTypeInner = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellInner())))); + // TypeAtoms related to (map)[]. This is to avoid passing down env argument when doing + // tableSubtypeComplement operation. + private final Supplier cellAtomicInnerMapping = new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from(union(Builder.getMappingType(), Builder.getUndefType()), + CellAtomicType.CellMutability.CELL_MUT_LIMITED), + this::addInitializedCellAtom + ); + private final Supplier atomCellInnerMapping = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicInnerMapping, this::cellAtomIndex); + private final Supplier listAtomicMapping = new ConcurrentLazySupplierWithCallback<>( + () -> new ListAtomicType(FixedLengthArray.empty(), basicSubType( + BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellInnerMapping())))), + this::addInitializedListAtom + ); + private final Supplier atomListMapping = + createTypeAtomSupplierFromCellAtomicSupplier(listAtomicMapping, this::listAtomIndex); + // 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 final Supplier cellAtomicInnerMappingRO = new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from(union(Builder.mappingRO(), Builder.getUndefType()), + CellAtomicType.CellMutability.CELL_MUT_LIMITED), + this::addInitializedCellAtom + ); + private final Supplier atomCellInnerMappingRO = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicInnerMappingRO, this::cellAtomIndex); + private final Supplier listAtomicMappingRO = new ConcurrentLazySupplierWithCallback<>( + () -> new ListAtomicType(FixedLengthArray.empty(), basicSubType( + BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellInnerMappingRO())))), + this::addInitializedListAtom + ); + + private final Supplier listSubtypeMapping = new ConcurrentLazySupplier<>( + () -> bddAtom(atomListMapping.get())); + private final Supplier mappingArray = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_LIST, BListSubType.createDelegate(listSubtypeMapping.get()))); + private final Supplier cellAtomicMappingArray = new ConcurrentLazySupplierWithCallback<>(() -> + CellAtomicType.from(mappingArray.get(), CellAtomicType.CellMutability.CELL_MUT_LIMITED), + this::addInitializedCellAtom); + private final Supplier atomCellMappingArray = new ConcurrentLazySupplier<>(() -> { + CellAtomicType cellAtom = cellAtomicMappingArray.get(); + return createTypeAtom(cellAtomIndex(cellAtom), cellAtom); + }); + private final Supplier cellSemTypeListSubtypeMapping = new ConcurrentLazySupplier<>(() -> + basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellMappingArray.get())))); + private final Supplier listAtomicThreeElement = new ConcurrentLazySupplierWithCallback<>( + () -> new ListAtomicType( + FixedLengthArray.from(new SemType[]{cellSemTypeListSubtypeMapping.get(), cellSemTypeVal.get()}, 3), + cellSemTypeVal.get()), + this::addInitializedListAtom + ); + private final Supplier atomListThreeElement = new ConcurrentLazySupplier<>(() -> { + ListAtomicType listAtomic = listAtomicThreeElement.get(); + return createTypeAtom(listAtomIndex(listAtomic), listAtomic); + }); + private final Supplier atomListMappingRO = + createTypeAtomSupplierFromCellAtomicSupplier(listAtomicMappingRO, this::listAtomIndex); + + private final Supplier cellAtomicUndef = new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from(Builder.getUndefType(), CellAtomicType.CellMutability.CELL_MUT_NONE), + this::addInitializedCellAtom + ); + private final Supplier atomCellUndef = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicUndef, this::cellAtomIndex); + private final Supplier cellSemTypeUndef = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellUndef.get())))); + + private final Supplier listSubtypeMappingRO = new ConcurrentLazySupplier<>(() -> bddAtom( + atomListMappingRO.get())); + private final Supplier mappingArrayRO = new ConcurrentLazySupplier<>(() -> basicSubType( + BT_LIST, BListSubType.createDelegate(listSubtypeMappingRO.get()))); + private final Supplier cellAtomicMappingArrayRO = new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from(mappingArrayRO.get(), CellAtomicType.CellMutability.CELL_MUT_LIMITED), + this::addInitializedCellAtom + ); + private final Supplier atomCellMappingArrayRO = new ConcurrentLazySupplier<>(() -> { + CellAtomicType cellAtom = cellAtomicMappingArrayRO.get(); + return createTypeAtom(cellAtomIndex(cellAtom), cellAtom); + }); + private final Supplier cellSemTypeListSubtypeMappingRO = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellMappingArrayRO.get())))); + private final Supplier listAtomicThreeElementRO = new ConcurrentLazySupplierWithCallback<>( + () -> new ListAtomicType( + FixedLengthArray.from(new SemType[]{cellSemTypeListSubtypeMappingRO.get(), cellSemTypeVal.get()}, + 3), + cellSemTypeUndef.get()), + this::addInitializedListAtom + ); + private final Supplier atomListThreeElementRO = new ConcurrentLazySupplier<>(() -> { + ListAtomicType listAtomic = listAtomicThreeElementRO.get(); + return createTypeAtom(listAtomIndex(listAtomic), listAtomic); + }); + + private final Supplier readonlyType = new ConcurrentLazySupplier<>(() -> 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)), + basicSubType(BT_TABLE, BTableSubType.createDelegate(bddAtom(atomListThreeElementRO.get()))), + basicSubType(BT_XML, XmlUtils.XML_SUBTYPE_RO) + )); + + private final ConcurrentLazySupplier innerReadOnly = + new ConcurrentLazySupplier<>(() -> union(readonlyType.get(), inner.get())); + private final Supplier cellAtomicInnerRO = new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from(innerReadOnly.get(), CellAtomicType.CellMutability.CELL_MUT_NONE), + this::addInitializedCellAtom + ); + private final Supplier atomCellInnerRO = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicInnerRO, this::cellAtomIndex); + private final Supplier cellSemTypeInnerRO = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellInnerRO.get())))); + private final Supplier listAtomicRO = new ConcurrentLazySupplierWithCallback<>( + () -> new ListAtomicType(FixedLengthArray.empty(), cellSemTypeInnerRO.get()), + this.initializedRecListAtoms::add + ); + private final Supplier mappingAtomicRO = new ConcurrentLazySupplierWithCallback<>( + () -> new MappingAtomicType(new String[]{}, new SemType[]{}, cellSemTypeInnerRO.get()), + initializedRecMappingAtoms::add + ); + // TypeAtoms related to [any|error, any|error]. This is to avoid passing down env argument when doing + // streamSubtypeComplement operation. + private final Supplier cellAtomicObjectMemberKind = + new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from( + union(getStringConst("field"), getStringConst("method")), + CellAtomicType.CellMutability.CELL_MUT_NONE), + this::addInitializedCellAtom); + private final Supplier atomCellObjectMemberKind = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicObjectMemberKind, this::cellAtomIndex); + private final Supplier cellSemTypeObjectMemberKind = new ConcurrentLazySupplier<>( + () -> Builder.basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellObjectMemberKind())))); + private final Supplier cellAtomicObjectMemberVisibility = + new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from( + union(getStringConst("public"), getStringConst("private")), + CellAtomicType.CellMutability.CELL_MUT_NONE), + this::addInitializedCellAtom); + private final Supplier atomCellObjectMemberVisibility = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicObjectMemberVisibility, this::cellAtomIndex); + private final Supplier cellSemTypeObjectMemberVisibility = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellObjectMemberVisibility())))); + private final Supplier mappingAtomicObjectMember = new ConcurrentLazySupplierWithCallback<>( + () -> new MappingAtomicType( + new String[]{"kind", "value", "visibility"}, + new SemType[]{cellSemTypeObjectMemberKind.get(), cellSemTypeVal.get(), + cellSemTypeObjectMemberVisibility.get()}, + cellSemTypeUndef.get()), + this::addInitializedMapAtom + ); + private final Supplier atomMappingObjectMember = + createTypeAtomSupplierFromCellAtomicSupplier(mappingAtomicObjectMember, this::mappingAtomIndex); + private final Supplier mappingSemTypeObjectMember = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_MAPPING, BMappingSubType.createDelegate(bddAtom(atomMappingObjectMember())))); + private final Supplier cellAtomicObjectMember = + new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from( + mappingSemTypeObjectMember.get(), CellAtomicType.CellMutability.CELL_MUT_UNLIMITED), + this::addInitializedCellAtom); + private final Supplier atomCellObjectMember = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicObjectMember, this::cellAtomIndex); + private final Supplier cellSemTypeObjectMember = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellObjectMember())))); + private final Supplier mappingAtomicObject = new ConcurrentLazySupplierWithCallback<>( + () -> new MappingAtomicType( + new String[]{"$qualifiers"}, new SemType[]{cellSemTypeVal.get()}, + cellSemTypeObjectMember.get() + ), + this::addInitializedMapAtom + ); + private final Supplier atomMappingObject = + createTypeAtomSupplierFromCellAtomicSupplier(mappingAtomicObject, this::mappingAtomIndex); + private final Supplier cellAtomicValRO = + new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from( + readonlyType.get(), CellAtomicType.CellMutability.CELL_MUT_NONE), + this::addInitializedCellAtom); + private final Supplier atomCellValRO = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicValRO, this::cellAtomIndex); + private final Supplier cellSemTypeValRo = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellValRO())))); + private final Supplier mappingAtomicObjectMemberRO = new ConcurrentLazySupplierWithCallback<>( + () -> new MappingAtomicType( + new String[]{"kind", "value", "visibility"}, + new SemType[]{cellSemTypeObjectMemberKind.get(), cellSemTypeValRo.get(), + cellSemTypeObjectMemberVisibility.get()}, + cellSemTypeUndef.get()), + this::addInitializedMapAtom + ); + private final Supplier atomMappingObjectMemberRO = + createTypeAtomSupplierFromCellAtomicSupplier(mappingAtomicObjectMemberRO, this::mappingAtomIndex); + private final Supplier mappingSemTypeObjectMemberRO = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_MAPPING, BMappingSubType.createDelegate(bddAtom(atomMappingObjectMemberRO())))); + private final Supplier cellAtomicObjectMemberRO = + new ConcurrentLazySupplierWithCallback<>( + () -> CellAtomicType.from( + mappingSemTypeObjectMemberRO.get(), CellAtomicType.CellMutability.CELL_MUT_NONE), + this::addInitializedCellAtom); + private final Supplier atomCellObjectMemberRO = + createTypeAtomSupplierFromCellAtomicSupplier(cellAtomicObjectMemberRO, this::cellAtomIndex); + private final Supplier cellSemTypeObjectMemberRO = new ConcurrentLazySupplier<>( + () -> basicSubType(BT_CELL, BCellSubType.createDelegate(bddAtom(atomCellObjectMemberRO())))); + private final Supplier mappingAtomicObjectRO = new ConcurrentLazySupplierWithCallback<>( + () -> new MappingAtomicType( + new String[]{"$qualifiers"}, new SemType[]{cellSemTypeVal.get()}, + cellSemTypeObjectMemberRO.get() + ), + initializedRecMappingAtoms::add + ); + + private final Supplier listAtomicTwoElement = new ConcurrentLazySupplierWithCallback<>( + () -> new ListAtomicType( + FixedLengthArray.from(new SemType[]{cellSemTypeVal.get()}, 2), + cellSemTypeUndef.get()), + this::addInitializedListAtom + ); + private final Supplier atomListTwoElement = new ConcurrentLazySupplier<>(() -> { + ListAtomicType listAtomic = listAtomicTwoElement.get(); + return createTypeAtom(listAtomIndex(listAtomic), listAtomic); + }); + + private PredefinedTypeEnv() { + } + + 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; + } + + private static BddNode bddSubtypeRo() { + return bddAtom(RecAtom.createRecAtom(0)); + } + + + public static synchronized PredefinedTypeEnv getInstance() { + if (instance == null) { + instance = new PredefinedTypeEnv(); + instance.initialize(); + } + return instance; + } + + private static Supplier createTypeAtomSupplierFromCellAtomicSupplier( + Supplier atomicTypeSupplier, IndexSupplier indexSupplier) { + return new ConcurrentLazySupplier<>(() -> { + E atomicType = atomicTypeSupplier.get(); + int index = indexSupplier.get(atomicType); + return createTypeAtom(index, atomicType); + }); + } + + private void initialize() { + // Initialize RecAtoms + mappingAtomicRO(); + listAtomicRO(); + mappingAtomicObjectRO(); + + // initialize atomic types + cellAtomicVal(); + cellAtomicNever(); + cellAtomicInner(); + cellAtomicInnerMapping(); + listAtomicMapping(); + cellAtomicInner(); + listAtomicMappingRO(); + cellAtomicInnerRO(); + initialized.set(true); + } + + 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(); + } + + CellAtomicType cellAtomicVal() { + return cellAtomicVal.get(); + } + + TypeAtom atomCellVal() { + return atomCellVal.get(); + } + + CellAtomicType cellAtomicNever() { + return cellAtomicNever.get(); + } + + TypeAtom atomCellNever() { + return atomCellNever.get(); + } + + CellAtomicType cellAtomicInner() { + return cellAtomicInner.get(); + } + + TypeAtom atomCellInner() { + return atomCellInner.get(); + } + + CellAtomicType cellAtomicInnerMapping() { + return cellAtomicInnerMapping.get(); + } + + TypeAtom atomCellInnerMapping() { + return atomCellInnerMapping.get(); + } + + CellAtomicType cellAtomicInnerMappingRO() { + return cellAtomicInnerMappingRO.get(); + } + + TypeAtom atomCellInnerMappingRO() { + return atomCellInnerMappingRO.get(); + } + + ListAtomicType listAtomicMapping() { + return listAtomicMapping.get(); + } + + TypeAtom atomListMapping() { + return atomListMapping.get(); + } + + ListAtomicType listAtomicMappingRO() { + return listAtomicMappingRO.get(); + } + + TypeAtom atomListMappingRO() { + return atomListMappingRO.get(); + } + + CellAtomicType cellAtomicInnerRO() { + return cellAtomicInnerRO.get(); + } + + TypeAtom atomCellInnerRO() { + return atomCellInnerRO.get(); + } + + CellAtomicType cellAtomicUndef() { + return cellAtomicUndef.get(); + } + + TypeAtom atomCellUndef() { + return atomCellUndef.get(); + } + + CellAtomicType cellAtomicValRO() { + return cellAtomicValRO.get(); + } + + TypeAtom atomCellValRO() { + return atomCellValRO.get(); + } + + MappingAtomicType mappingAtomicObjectMemberRO() { + return mappingAtomicObjectMemberRO.get(); + } + + TypeAtom atomMappingObjectMemberRO() { + return atomMappingObjectMemberRO.get(); + } + + CellAtomicType cellAtomicObjectMemberRO() { + return cellAtomicObjectMemberRO.get(); + } + + TypeAtom atomCellObjectMemberRO() { + return atomCellObjectMemberRO.get(); + } + + CellAtomicType cellAtomicObjectMemberKind() { + return cellAtomicObjectMemberKind.get(); + } + + TypeAtom atomCellObjectMemberKind() { + return atomCellObjectMemberKind.get(); + } + + CellAtomicType cellAtomicObjectMemberVisibility() { + return cellAtomicObjectMemberVisibility.get(); + } + + TypeAtom atomCellObjectMemberVisibility() { + return atomCellObjectMemberVisibility.get(); + } + + MappingAtomicType mappingAtomicObjectMember() { + return mappingAtomicObjectMember.get(); + } + + TypeAtom atomMappingObjectMember() { + return atomMappingObjectMember.get(); + } + + CellAtomicType cellAtomicObjectMember() { + return cellAtomicObjectMember.get(); + } + + TypeAtom atomCellObjectMember() { + return atomCellObjectMember.get(); + } + + MappingAtomicType mappingAtomicObject() { + return mappingAtomicObject.get(); + } + + TypeAtom atomMappingObject() { + return atomMappingObject.get(); + } + + ListAtomicType listAtomicRO() { + return listAtomicRO.get(); + } + + MappingAtomicType mappingAtomicRO() { + return mappingAtomicRO.get(); + } + + MappingAtomicType mappingAtomicObjectRO() { + return mappingAtomicObjectRO.get(); + } + + TypeAtom atomListThreeElement() { + return atomListThreeElement.get(); + } + + TypeAtom atomListThreeElementRO() { + return atomListThreeElementRO.get(); + } + + SemType readonlyType() { + return readonlyType.get(); + } + + // Due to some reason SpotBug thinks this method is overrideable if we don't put final here as well. + final void initializeEnv(Env env) { + assert initialized.get() : "PredefinedTypeEnv has not fully initialized, check concurrency issues"; + 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()); + } + + SemType cellSemTypeInner() { + return cellSemTypeInner.get(); + } + + public Atom atomListTwoElement() { + return atomListTwoElement.get(); + } + + @FunctionalInterface + private interface IndexSupplier { + + int get(E atomicType); + } + + 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..f302a7dc3c45 --- /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.11.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..d185c96b6c7f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemType.java @@ -0,0 +1,71 @@ +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.internal.types.semtype.ImmutableSemType; +import io.ballerina.runtime.internal.types.semtype.MutableSemType; +import io.ballerina.runtime.internal.types.semtype.SemTypeHelper; + +/** + * Represent a type in runtime. + * + * @since 2201.11.0 + */ +public sealed class SemType extends BasicTypeBitSet + permits io.ballerina.runtime.internal.types.BType, ImmutableSemType { + + private int some; + private SubType[] subTypeData; + + protected SemType(int all, int some, SubType[] subTypeData) { + super(all); + this.some = some; + this.subTypeData = subTypeData; + } + + protected SemType() { + this(-1, -1, null); + } + + public static SemType from(int all) { + return new SemType(all, 0, null); + } + + public static SemType from(int all, int some, SubType[] subTypes) { + return new SemType(all, some, subTypes); + } + + public final int some() { + assert some != -1 : "SemType created by no arg constructor must be initialized with setSome"; + return some; + } + + public final SubType[] subTypeData() { + return subTypeData; + } + + 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)]; + } + + protected void setSome(int some, SubType[] subTypeData) { + this.some = some; + this.subTypeData = subTypeData; + } + + public static SemType tryInto(Type type) { + if (type instanceof MutableSemType mutableSemType) { + mutableSemType.updateInnerSemTypeIfNeeded(); + } + return (SemType) type; + } + + @Override + public String toString() { + return SemTypeHelper.stringRepr(this); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ShapeAnalyzer.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ShapeAnalyzer.java new file mode 100644 index 000000000000..99bc4c9a51f5 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ShapeAnalyzer.java @@ -0,0 +1,72 @@ +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BValue; +import io.ballerina.runtime.internal.types.TypeWithAcceptedType; +import io.ballerina.runtime.internal.types.TypeWithShape; +import io.ballerina.runtime.internal.values.DecimalValue; + +import java.util.Optional; + +/** + * Utility class for performing shape related operations. + * + * @since 2201.11.0 + */ +public class ShapeAnalyzer { + + private ShapeAnalyzer() { + } + + public static Optional acceptedTypeOf(Context cx, Type typeDesc) { + if (typeDesc instanceof TypeWithAcceptedType typeWithAcceptedType) { + return typeWithAcceptedType.acceptedTypeOf(cx); + } + return Optional.of(SemType.tryInto(typeDesc)); + } + + public static Optional shapeOf(Context cx, Object object) { + if (object == null) { + return Optional.of(Builder.getNilType()); + } else if (object instanceof DecimalValue decimalValue) { + return Optional.of(Builder.getDecimalConst(decimalValue.value())); + } else if (object instanceof Double doubleValue) { + return Optional.of(Builder.getFloatConst(doubleValue)); + } else if (object instanceof Number intValue) { + long value = + intValue instanceof Byte byteValue ? Byte.toUnsignedLong(byteValue) : intValue.longValue(); + return Optional.of(Builder.getIntConst(value)); + } else if (object instanceof Boolean booleanValue) { + return Optional.of(Builder.getBooleanConst(booleanValue)); + } else if (object instanceof BString stringValue) { + return Optional.of(Builder.getStringConst(stringValue.getValue())); + } else if (object instanceof BValue bValue) { + Type type = bValue.getType(); + if (type instanceof TypeWithShape typeWithShape) { + return typeWithShape.shapeOf(cx, ShapeAnalyzer::shapeOf, object); + } else { + return Optional.empty(); + } + } + return Optional.empty(); + } + + public static Optional inherentTypeOf(Context cx, Object object) { + if (object instanceof BValue bValue) { + return bValue.inherentTypeOf(cx); + } + if (object == null) { + return Optional.of(Builder.getNilType()); + } else if (object instanceof Double doubleValue) { + return Optional.of(Builder.getFloatConst(doubleValue)); + } else if (object instanceof Number intValue) { + long value = + intValue instanceof Byte byteValue ? Byte.toUnsignedLong(byteValue) : intValue.longValue(); + return Optional.of(Builder.getIntConst(value)); + } else if (object instanceof Boolean booleanValue) { + return Optional.of(Builder.getBooleanConst(booleanValue)); + } + return Optional.empty(); + } +} 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..85fabfa2303b --- /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.11.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..02080171937e --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/TypeAtom.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; + +/** + * Represent a TypeAtom. Each operand of a type operation could be thought of as + * an atom + * + * @param index unique index within the {@code Env} + * @param atomicType atomic type representing the actual type represented by + * this atom. + * @since 2201.11.0 + */ +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/types/semtype/TypeCheckCache.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/TypeCheckCache.java new file mode 100644 index 000000000000..cf9bc820da2a --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/TypeCheckCache.java @@ -0,0 +1,41 @@ +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.api.types.Type; + +import java.util.Map; +import java.util.Optional; +import java.util.WeakHashMap; + +/** + * Generalized implementation of type check result cache. It is okay to access + * this from multiple threads but makes no + * guarantee about the consistency of the cache under parallel access. Given + * result don't change due to race conditions + * this should eventually become consistent. + * + * @param Type of the type descriptor which owns this cache + * @since 2201.11.0 + */ +public class TypeCheckCache { + + // Not synchronizing this should be fine since race conditions don't lead to inconsistent results. (i.e. results + // of doing multiple type checks are agnostic to the order of execution). Data races shouldn't lead to tearing in + // 64-bit JVMs. + private final Map cachedResults = new WeakHashMap<>(); + private final T owner; + + public TypeCheckCache(T owner) { + this.owner = owner; + } + + public Optional cachedTypeCheckResult(T other) { + if (other.equals(owner)) { + return Optional.of(true); + } + return Optional.ofNullable(cachedResults.get(other)); + } + + public void cacheTypeCheckResult(T other, boolean result) { + cachedResults.put(other, result); + } +} 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/BError.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BError.java index 88b368d827f4..5dd81119cabd 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BError.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BError.java @@ -17,8 +17,14 @@ */ package io.ballerina.runtime.api.values; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; +import io.ballerina.runtime.internal.types.TypeWithShape; + import java.io.PrintWriter; import java.util.List; +import java.util.Optional; /** *

@@ -83,4 +89,9 @@ public void printStackTrace(PrintWriter printWriter) { */ public abstract List getCallStack(); + @Override + public Optional inherentTypeOf(Context cx) { + TypeWithShape type = (TypeWithShape) getType(); + return type.inherentTypeOf(cx, ShapeAnalyzer::inherentTypeOf, this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BRegexpValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BRegexpValue.java index 7de5799b3f45..ed62af17014f 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BRegexpValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BRegexpValue.java @@ -17,8 +17,11 @@ */ package io.ballerina.runtime.api.values; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.RegExpDisjunction; +import java.util.Optional; + /** *

* Represents RegexpValue. @@ -33,4 +36,6 @@ public interface BRegexpValue extends BValue { public RegExpDisjunction getRegExpDisjunction(); BTypedesc getTypedesc(); + + Optional shapeOf(); } 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 b7308732d7bc..08d9a54c4e23 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..c23f563ff6d1 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,8 +18,11 @@ package io.ballerina.runtime.api.values; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; import java.util.Map; +import java.util.Optional; /** *

@@ -58,4 +61,20 @@ default String informalStringValue(BLink parent) { String expressionStringValue(BLink parent); Type getType(); + + /** + * Basic type of the value. + * + * @return {@code SemType} representing the value's basic type + */ + default SemType widenedType() { + // This is wrong since we are actually returning the actual (narrowed) type of the value. But since this is + // used only as an optimization (to avoid recalculating singleton type) in the type checker this is better + // than caching the widened types as well. + return SemType.tryInto(getType()); + } + + default Optional inherentTypeOf(Context cx) { + return Optional.empty(); + } } 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 31b9372261ce..2dedd7250a87 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 @@ -22,14 +22,19 @@ 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.MapType; import io.ballerina.runtime.api.types.MethodType; -import io.ballerina.runtime.api.types.PredefinedTypes; +import io.ballerina.runtime.api.types.ReadonlyType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; -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.Builder; +import io.ballerina.runtime.api.types.semtype.CacheableTypeDescriptor; +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.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BObject; @@ -37,64 +42,39 @@ 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.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.types.TypeWithShape; import io.ballerina.runtime.internal.utils.ErrorUtils; -import io.ballerina.runtime.internal.values.ArrayValue; import io.ballerina.runtime.internal.values.DecimalValue; import io.ballerina.runtime.internal.values.DecimalValueKind; -import io.ballerina.runtime.internal.values.ErrorValue; import io.ballerina.runtime.internal.values.HandleValue; -import io.ballerina.runtime.internal.values.MapValue; -import io.ballerina.runtime.internal.values.MapValueImpl; +import io.ballerina.runtime.internal.values.RefValue; 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; -import io.ballerina.runtime.internal.values.XmlComment; -import io.ballerina.runtime.internal.values.XmlItem; -import io.ballerina.runtime.internal.values.XmlPi; import io.ballerina.runtime.internal.values.XmlSequence; -import io.ballerina.runtime.internal.values.XmlText; 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.function.Predicate; +import java.util.stream.Stream; import static io.ballerina.runtime.api.constants.RuntimeConstants.BALLERINA_BUILTIN_PKG_PREFIX; import static io.ballerina.runtime.api.constants.RuntimeConstants.BBYTE_MAX_VALUE; @@ -109,8 +89,6 @@ 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.PredefinedTypes.TYPE_ANY; -import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_ANYDATA; import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_BOOLEAN; import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_BYTE; import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_DECIMAL; @@ -122,15 +100,8 @@ import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_INT_UNSIGNED_16; import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_INT_UNSIGNED_32; import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_INT_UNSIGNED_8; -import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_JSON; import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_NULL; -import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_READONLY_JSON; -import static io.ballerina.runtime.api.types.PredefinedTypes.TYPE_STRING; 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; import static io.ballerina.runtime.internal.utils.CloneUtils.getErrorMessage; /** @@ -143,34 +114,39 @@ public final class TypeChecker { private static final byte MAX_TYPECAST_ERROR_COUNT = 20; private static final String REG_EXP_TYPENAME = "RegExp"; + private static final ThreadLocal threadContext = + ThreadLocal.withInitial(() -> Context.from(Env.getInstance())); public static Object checkCast(Object sourceVal, Type targetType) { List errors = new ArrayList<>(); - Type sourceType = getImpliedType(getType(sourceVal)); - if (checkIsType(errors, sourceVal, sourceType, targetType)) { + if (checkIsType(sourceVal, targetType)) { return sourceVal; } - - if (sourceType.getTag() <= TypeTags.BOOLEAN_TAG && targetType.getTag() <= TypeTags.BOOLEAN_TAG) { - return TypeConverter.castValues(targetType, sourceVal); - } - - // if the source is a numeric value and the target type is a union, try to find a matching - // member. - if (sourceType.getTag() <= TypeTags.BOOLEAN_TAG && targetType.getTag() == TypeTags.UNION_TAG) { - for (Type memberType : ((BUnionType) targetType).getMemberTypes()) { - try { - return TypeConverter.castValues(memberType, sourceVal); - } catch (Exception e) { - //ignore and continue + Type sourceType = getType(sourceVal); + if (Core.containsBasicType(SemType.tryInto(sourceType), ConvertibleCastMaskHolder.CONVERTIBLE_CAST_MASK) && + Core.containsBasicType(SemType.tryInto(targetType), ConvertibleCastMaskHolder.CONVERTIBLE_CAST_MASK)) { + // We need to maintain order for these? + if (targetType instanceof BUnionType unionType) { + for (Type memberType : unionType.getMemberTypes()) { + try { + return TypeConverter.castValues(memberType, sourceVal); + } catch (Exception e) { + //ignore and continue + } } + } else { + return TypeConverter.castValues(targetType, sourceVal); } } - throw createTypeCastError(sourceVal, targetType, errors); } + public static Context context() { + // We are pinning each context to thread. We can't use the same context with multiple type checks concurrently + return threadContext.get(); + } + public static long anyToInt(Object sourceVal) { return TypeConverter.anyToIntCast(sourceVal, () -> ErrorUtils.createTypeCastError(sourceVal, TYPE_INT)); @@ -281,7 +257,14 @@ 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(); + Type sourceType = getType(sourceVal); + if (isSubType(sourceType, targetType)) { + return true; + } + SemType sourceSemType = SemType.tryInto(sourceType); + return couldInherentTypeBeDifferent(sourceSemType) && + isSubTypeWithInherentType(cx, sourceVal, SemType.tryInto(targetType)); } /** @@ -294,22 +277,15 @@ 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); - } - } + return checkIsType(sourceVal, targetType); + } - if (isMutable(sourceVal, sourceType)) { - return false; + // This is just an optimization since shapes are not cached, when in doubt return false + private static boolean couldInherentTypeBeDifferent(SemType type) { + if (type instanceof TypeWithShape typeWithShape) { + return typeWithShape.couldInherentTypeBeDifferent(); } - - return checkIsLikeOnValue(errors, sourceVal, sourceType, targetType, new ArrayList<>(), false, null); + return true; } /** @@ -332,8 +308,27 @@ 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, - null); + Context cx = context(); + SemType shape = ShapeAnalyzer.shapeOf(cx, sourceValue).orElseThrow(); + SemType targetSemType = ShapeAnalyzer.acceptedTypeOf(cx, targetType).orElseThrow(); + if (Core.isSubType(cx, shape, NumericTypeHolder.NUMERIC_TYPE) && allowNumericConversion) { + targetSemType = appendNumericConversionTypes(targetSemType); + } + return Core.isSubType(cx, shape, targetSemType); + } + + private static SemType appendNumericConversionTypes(SemType semType) { + SemType result = semType; + // We can represent any int value as a float or a decimal. This is to avoid the overhead of creating + // enumerable semtypes for them + if (Core.containsBasicType(semType, Builder.getIntType())) { + result = Core.union(Core.union(Builder.getDecimalType(), Builder.getFloatType()), result); + } + result = Core.union(result, Core.floatToInt(semType)); + result = Core.union(result, Core.floatToDecimal(semType)); + result = Core.union(result, Core.decimalToInt(semType)); + result = Core.union(result, Core.decimalToFloat(semType)); + return result; } /** @@ -344,29 +339,34 @@ public static boolean checkIsLikeType(Object sourceValue, Type targetType, boole * @return true if the two types are same; false otherwise */ public static boolean isSameType(Type sourceType, Type targetType) { - return sourceType == targetType || sourceType.equals(targetType); + return Core.isSameType(context(), SemType.tryInto(sourceType), SemType.tryInto(targetType)); } public static Type getType(Object value) { - if (value == null) { - return TYPE_NULL; - } else if (value instanceof Number) { - if (value instanceof Long) { - return TYPE_INT; - } else if (value instanceof Double) { - return TYPE_FLOAT; - } else if (value instanceof Integer || value instanceof Byte) { - return TYPE_BYTE; + if (value instanceof BValue bValue) { + if (!(value instanceof BObject bObject)) { + return bValue.getType(); } - } else if (value instanceof BString) { - return TYPE_STRING; - } else if (value instanceof Boolean) { - return TYPE_BOOLEAN; - } else if (value instanceof BObject bObject) { return bObject.getOriginalType(); } + if (value == null) { + return TYPE_NULL; + } else if (value instanceof Number number) { + return getNumberType(number); + } else if (value instanceof Boolean booleanValue) { + return BBooleanType.singletonType(booleanValue); + } + throw new IllegalArgumentException("unexpected value type"); + } - return ((BValue) value).getType(); + private static Type getNumberType(Number number) { + if (number instanceof Double) { + return TYPE_FLOAT; + } + if (number instanceof Integer || number instanceof Byte) { + return TYPE_BYTE; + } + return TYPE_INT; } /** @@ -380,18 +380,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. * @@ -411,7 +399,7 @@ public static boolean checkDecimalExactEqual(DecimalValue lhsValue, DecimalValue * @param decimalValue The decimal value being checked * @return True if the decimal value is a real number. */ - private static boolean isDecimalRealNumber(DecimalValue decimalValue) { + static boolean isDecimalRealNumber(DecimalValue decimalValue) { return decimalValue.valueKind == DecimalValueKind.ZERO || decimalValue.valueKind == DecimalValueKind.OTHER; } @@ -434,54 +422,36 @@ public static boolean isReferenceEqual(Object lhsValue, Object rhsValue) { return false; } - Type lhsType = getImpliedType(getType(lhsValue)); - Type rhsType = getImpliedType(getType(rhsValue)); - - return switch (lhsType.getTag()) { - case TypeTags.FLOAT_TAG -> { - if (rhsType.getTag() != TypeTags.FLOAT_TAG) { - yield false; - } - yield lhsValue.equals(((Number) rhsValue).doubleValue()); - } - case TypeTags.DECIMAL_TAG -> { - if (rhsType.getTag() != TypeTags.DECIMAL_TAG) { - yield false; - } - yield checkDecimalExactEqual((DecimalValue) lhsValue, (DecimalValue) rhsValue); - } - case TypeTags.INT_TAG, - TypeTags.BYTE_TAG, - TypeTags.BOOLEAN_TAG, - TypeTags.STRING_TAG -> isEqual(lhsValue, rhsValue); - case TypeTags.XML_TAG, - TypeTags.XML_COMMENT_TAG, - TypeTags.XML_ELEMENT_TAG, - TypeTags.XML_PI_TAG, - TypeTags.XML_TEXT_TAG -> { - if (!TypeTags.isXMLTypeTag(rhsType.getTag())) { - yield false; - } - yield isXMLValueRefEqual((XmlValue) lhsValue, (XmlValue) rhsValue); - } - case TypeTags.HANDLE_TAG -> { - if (rhsType.getTag() != TypeTags.HANDLE_TAG) { - yield false; - } - yield isHandleValueRefEqual(lhsValue, rhsValue); - } - case TypeTags.FUNCTION_POINTER_TAG -> lhsType.getPackage().equals(rhsType.getPackage()) && - lhsType.getName().equals(rhsType.getName()) && rhsType.equals(lhsType); - default -> { - if (lhsValue instanceof RegExpValue lhsRegExpValue && rhsValue instanceof RegExpValue) { - yield lhsRegExpValue.equals(rhsValue, new HashSet<>()); - } - yield false; - } - }; + Context cx = context(); + SemType lhsType = widenedType(cx, lhsValue); + SemType rhsType = widenedType(cx, rhsValue); + if (isSimpleBasicSemType(lhsType)) { + return isSimpleBasicValuesEqual(lhsValue, rhsValue); + } + Predicate basicTypePredicate = + (basicType) -> Core.isSubType(cx, lhsType, basicType) && Core.isSubType(cx, rhsType, basicType); + if (basicTypePredicate.test(Builder.getStringType())) { + return isEqual(lhsValue, rhsValue); + } + if (basicTypePredicate.test(Builder.getXmlType())) { + return isXMLValueRefEqual((XmlValue) lhsValue, (XmlValue) rhsValue); + } + if (basicTypePredicate.test(Builder.getHandleType())) { + return isHandleValueRefEqual(lhsValue, rhsValue); + } + if (basicTypePredicate.test(Builder.getFunctionType())) { + return isFunctionPointerEqual(getImpliedType(getType(lhsValue)), getImpliedType(getType(rhsValue))); + } + if (basicTypePredicate.test(Builder.getRegexType())) { + RegExpValue lhsReg = (RegExpValue) lhsValue; + RegExpValue rhsReg = (RegExpValue) rhsValue; + return lhsReg.equals(rhsReg, new HashSet<>()); + } + // Other types have storage identity so == test should have passed + return false; } - private static boolean isXMLValueRefEqual(XmlValue lhsValue, XmlValue rhsValue) { + static boolean isXMLValueRefEqual(XmlValue lhsValue, XmlValue rhsValue) { boolean isLhsXmlSequence = lhsValue.getNodeType() == XmlNodeType.SEQUENCE; boolean isRhsXmlSequence = rhsValue.getNodeType() == XmlNodeType.SEQUENCE; @@ -519,6 +489,38 @@ private static boolean isXMLSequenceRefEqual(XmlSequence lhsValue, XmlSequence r return lhsIter.hasNext() == rhsIter.hasNext(); } + private static boolean isFunctionPointerEqual(Type lhsType, Type rhsType) { + return lhsType.getPackage().equals(rhsType.getPackage()) && + lhsType.getName().equals(rhsType.getName()) && rhsType.equals(lhsType); + } + + private static boolean isSimpleBasicValuesEqual(Object v1, Object v2) { + Context cx = context(); + SemType v1Ty = widenedType(cx, v1); + if (!isSimpleBasicSemType(v1Ty)) { + return false; + } + + SemType v2Ty = widenedType(cx, v2); + if (!isSimpleBasicSemType(v2Ty)) { + return false; + } + + if (!Core.isSameType(cx, v1Ty, v2Ty)) { + return false; + } + + if (Core.isSubType(cx, v1Ty, Builder.getDecimalType())) { + return checkDecimalExactEqual((DecimalValue) v1, (DecimalValue) v2); + } + if (Core.isSubType(cx, v1Ty, Builder.getIntType())) { + Number n1 = (Number) v1; + Number n2 = (Number) v2; + return n1.longValue() == n2.longValue(); + } + return v1.equals(v2); + } + /** * Get the typedesc of a value. * @@ -530,7 +532,7 @@ public static TypedescValue getTypedesc(Object value) { if (type == null) { return null; } - if (isSimpleBasicType(type)) { + if (belongToSingleBasicTypeOrString(type)) { return new TypedescValueImpl(new BFiniteType(value.toString(), Set.of(value), 0)); } if (value instanceof BRefValue bRefValue) { @@ -562,2207 +564,315 @@ public static Object getAnnotValue(TypedescValue typedescValue, BString annotTag * @return flag indicating the equivalence of the two types */ public static boolean checkIsType(Type sourceType, Type targetType) { - return checkIsType(sourceType, targetType, null); + return isSubType(sourceType, targetType); } @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; - } - - 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 -> - 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 -> - checkIsType(sourceType, ((BIntersectionType) targetType).getEffectiveType(), unresolvedTypes); - case TypeTags.TYPE_REFERENCED_TYPE_TAG -> - checkIsType(sourceType, ((BTypeReferenceType) targetType).getReferredType(), unresolvedTypes); - default -> checkIsRecursiveType(sourceType, targetType, - unresolvedTypes == null ? new ArrayList<>() : 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); - } - - 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 -> isInherentlyImmutableType(sourceType) || sourceType.isReadOnly(); - default -> checkIsRecursiveTypeOnValue(sourceVal, sourceType, targetType, sourceTypeTag, targetTypeTag, - unresolvedTypes == null ? new ArrayList<>() : unresolvedTypes); - }; - } - - // 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) { - return switch (targetType.getTag()) { - case TypeTags.MAP_TAG -> checkIsMapType(sourceType, (BMapType) targetType, unresolvedTypes); - case TypeTags.STREAM_TAG -> checkIsStreamType(sourceType, (BStreamType) targetType, unresolvedTypes); - case TypeTags.TABLE_TAG -> checkIsTableType(sourceType, (BTableType) targetType, unresolvedTypes); - case TypeTags.JSON_TAG -> checkIsJSONType(sourceType, unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG -> checkIsRecordType(sourceType, (BRecordType) targetType, unresolvedTypes); - case TypeTags.FUNCTION_POINTER_TAG -> checkIsFunctionType(sourceType, (BFunctionType) targetType); - case TypeTags.ARRAY_TAG -> checkIsArrayType(sourceType, (BArrayType) targetType, unresolvedTypes); - case TypeTags.TUPLE_TAG -> checkIsTupleType(sourceType, (BTupleType) targetType, unresolvedTypes); - case TypeTags.UNION_TAG -> checkIsUnionType(sourceType, (BUnionType) targetType, unresolvedTypes); - case TypeTags.OBJECT_TYPE_TAG -> - checkObjectEquivalency(sourceType, (BObjectType) targetType, unresolvedTypes); - case TypeTags.FINITE_TYPE_TAG -> checkIsFiniteType(sourceType, (BFiniteType) targetType); - case TypeTags.FUTURE_TAG -> checkIsFutureType(sourceType, (BFutureType) targetType, unresolvedTypes); - case TypeTags.ERROR_TAG -> checkIsErrorType(sourceType, (BErrorType) targetType, unresolvedTypes); - case TypeTags.TYPEDESC_TAG -> checkTypeDescType(sourceType, (BTypedescType) targetType, unresolvedTypes); - case TypeTags.XML_TAG -> checkIsXMLType(sourceType, targetType, unresolvedTypes); - // other non-recursive types shouldn't reach here - default -> false; - }; - } - - private static boolean checkIsRecursiveTypeOnValue(Object sourceVal, Type sourceType, Type targetType, - int sourceTypeTag, int targetTypeTag, - List unresolvedTypes) { - return switch (targetTypeTag) { - case TypeTags.ANYDATA_TAG -> { - if (sourceTypeTag == TypeTags.OBJECT_TYPE_TAG) { - yield false; - } - yield checkRecordBelongsToAnydataType((MapValue) sourceVal, (BRecordType) sourceType, unresolvedTypes); - } - case TypeTags.MAP_TAG -> checkIsMapType(sourceVal, sourceType, (BMapType) targetType, unresolvedTypes); - case TypeTags.JSON_TAG -> checkIsMapType(sourceVal, sourceType, - new BMapType(targetType.isReadOnly() ? TYPE_READONLY_JSON : - TYPE_JSON), unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG -> - checkIsRecordType(sourceVal, sourceType, (BRecordType) targetType, unresolvedTypes); - case TypeTags.UNION_TAG -> { - for (Type type : ((BUnionType) targetType).getMemberTypes()) { - if (checkIsType(sourceVal, sourceType, type, unresolvedTypes)) { - yield true; - } - } - yield false; - } - case TypeTags.OBJECT_TYPE_TAG -> - checkObjectEquivalency(sourceVal, sourceType, (BObjectType) targetType, unresolvedTypes); - default -> false; - }; - } - - private static boolean isFiniteTypeMatch(BFiniteType sourceType, Type targetType) { - for (Object bValue : sourceType.valueSpace) { - if (!checkIsType(bValue, targetType)) { - return false; - } - } - return true; + return isSubType(sourceType, targetType); } - private static boolean isUnionTypeMatch(BUnionType sourceType, Type targetType, List unresolvedTypes) { - for (Type type : sourceType.getMemberTypes()) { - if (!checkIsType(type, targetType, unresolvedTypes)) { - return false; - } - } - return true; - } - - private static boolean checkIsUnionType(Type sourceType, BUnionType targetType, List unresolvedTypes) { - // If we encounter two types that we are still resolving, then skip it. - // This is done to avoid recursive checking of the same type. - sourceType = getImpliedType(sourceType); - TypePair pair = new TypePair(sourceType, targetType); - if (unresolvedTypes.contains(pair)) { - return true; - } - unresolvedTypes.add(pair); - - return switch (sourceType.getTag()) { - case TypeTags.UNION_TAG, - TypeTags.JSON_TAG, - TypeTags.ANYDATA_TAG -> isUnionTypeMatch((BUnionType) sourceType, targetType, unresolvedTypes); - case TypeTags.FINITE_TYPE_TAG -> isFiniteTypeMatch((BFiniteType) sourceType, targetType); - default -> { - for (Type type : targetType.getMemberTypes()) { - if (checkIsType(sourceType, type, unresolvedTypes)) { - yield true; - } - } - yield false; - } - }; + /** + * Check if two decimal values are equal in value. + * + * @param lhsValue The value on the left hand side + * @param rhsValue The value of the right hand side + * @return True if values are equal, else false. + */ + public static boolean checkDecimalEqual(DecimalValue lhsValue, DecimalValue rhsValue) { + return isDecimalRealNumber(lhsValue) && isDecimalRealNumber(rhsValue) && + lhsValue.decimalValue().compareTo(rhsValue.decimalValue()) == 0; } - 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; - } + public static boolean isNumericType(Type type) { + return Core.isSubType(context(), SemType.tryInto(type), NumericTypeHolder.NUMERIC_TYPE); } - private static boolean checkIsMapType(Object sourceVal, Type sourceType, BMapType targetType, - List unresolvedTypes) { - Type targetConstrainedType = targetType.getConstrainedType(); - sourceType = getImpliedType(sourceType); - return switch (sourceType.getTag()) { - case TypeTags.MAP_TAG -> checkConstraints(((BMapType) sourceType).getConstrainedType(), - targetConstrainedType, unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG -> checkIsMapType((MapValue) sourceVal, (BRecordType) sourceType, - unresolvedTypes, targetConstrainedType); - default -> false; - }; + public static boolean isByteLiteral(long longValue) { + return (longValue >= BBYTE_MIN_VALUE && longValue <= BBYTE_MAX_VALUE); } - 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 methods - return checkIsType(sourceType.restFieldType, targetConstrainedType, unresolvedTypes); + private static boolean isSubTypeWithInherentType(Context cx, Object sourceValue, SemType target) { + return ShapeAnalyzer.inherentTypeOf(cx, sourceValue) + .map(source -> !Core.isEmpty(cx, source) && Core.isSubType(cx, source, target)) + // OR else do the normal type check by taking the shape of + .orElse(false); } - private static boolean checkIsXMLType(Type sourceType, Type targetType, List unresolvedTypes) { - sourceType = getImpliedType(sourceType); - int sourceTag = sourceType.getTag(); - if (sourceTag == TypeTags.FINITE_TYPE_TAG) { - return isFiniteTypeMatch((BFiniteType) sourceType, targetType); - } - - BXmlType target = ((BXmlType) targetType); - if (sourceTag == TypeTags.XML_TAG) { - Type targetConstraint = getRecursiveTargetConstraintType(target); - BXmlType source = (BXmlType) sourceType; - if (source.constraint.getTag() == TypeTags.NEVER_TAG) { - if (targetConstraint.getTag() == TypeTags.UNION_TAG) { - return checkIsUnionType(sourceType, (BUnionType) targetConstraint, unresolvedTypes); - } - return targetConstraint.getTag() == TypeTags.XML_TEXT_TAG || - targetConstraint.getTag() == TypeTags.NEVER_TAG; - } - return checkIsType(source.constraint, targetConstraint, unresolvedTypes); - } - if (TypeTags.isXMLTypeTag(sourceTag)) { - return checkIsType(sourceType, target.constraint, unresolvedTypes); + private static boolean isSubType(Type source, Type target) { + if (source instanceof CacheableTypeDescriptor sourceCacheableType && + target instanceof CacheableTypeDescriptor targetCacheableType) { + return isSubTypeWithCache(sourceCacheableType, targetCacheableType); } - return false; + // This is really a workaround for Standard libraries that create record types that are not the "same". But + // with the same name and expect them to be same. + return isSubTypeInner(context(), source, target); } - 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 boolean isSubTypeInner(Context cx, Type source, Type target) { + SemType sourceSemType = SemType.tryInto(source); + SemType targetSemType = SemType.tryInto(target); + return Core.isSubType(cx, sourceSemType, targetSemType); } - private static List getWideTypeComponents(BRecordType recType) { - List types = new ArrayList<>(); - for (Field f : recType.getFields().values()) { - types.add(f.getFieldType()); + private static boolean isSubTypeWithCache(CacheableTypeDescriptor source, CacheableTypeDescriptor target) { + Context cx = context(); + if (!source.shouldCache() || !target.shouldCache()) { + return isSubTypeInner(cx, source, target); } - 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; + Optional cachedResult = source.cachedTypeCheckResult(cx, target); + if (cachedResult.isPresent()) { + assert cachedResult.get() == isSubTypeInner(cx, source, target); + return cachedResult.get(); } - return checkConstraints(((BStreamType) sourceType).getConstrainedType(), targetType.getConstrainedType(), - unresolvedTypes) - && checkConstraints(((BStreamType) sourceType).getCompletionType(), targetType.getCompletionType(), - unresolvedTypes); + boolean result = isSubTypeInner(cx, source, target); + source.cacheTypeCheckResult(target, result); + return result; } - 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; + private static SemType widenedType(Context cx, Object value) { + if (value instanceof BValue bValue) { + return bValue.widenedType(); } - - 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); + if (value == null) { + return Builder.getNilType(); + } else if (value instanceof Double) { + return Builder.getFloatType(); + } else if (value instanceof Number) { + return Builder.getIntType(); + } else if (value instanceof BString) { + return Builder.getStringType(); + } else if (value instanceof Boolean) { + return Builder.getBooleanType(); } - - return Arrays.equals(srcTableType.getFieldNames(), targetType.getFieldNames()); + throw new IllegalArgumentException("Unexpected object type"); } - 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; - } + public static boolean isInherentlyImmutableType(Type sourceType) { + // readonly part is there to match to old API + return + Core.isSubType(context(), SemType.tryInto(sourceType), + InherentlyImmutableTypeHolder.INHERENTLY_IMMUTABLE_TYPE) || + sourceType instanceof ReadonlyType; } - 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)) { + // NOTE: this is not the same as selectively immutable as it stated in the spec + public static boolean isSelectivelyImmutableType(Type type, Set unresolvedTypes) { + if (!unresolvedTypes.add(type)) { 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: + switch (type.getTag()) { + case TypeTags.ANY_TAG: + case TypeTags.ANYDATA_TAG: case TypeTags.JSON_TAG: + case TypeTags.XML_TAG: + case TypeTags.XML_COMMENT_TAG: + case TypeTags.XML_ELEMENT_TAG: + case TypeTags.XML_PI_TAG: + case TypeTags.READONLY_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)) { + Type elementType = ((BArrayType) type).getElementType(); + return isInherentlyImmutableType(elementType) || + isSelectivelyImmutableType(elementType, unresolvedTypes); + case TypeTags.TUPLE_TAG: + BTupleType tupleType = (BTupleType) type; + for (Type tupMemType : tupleType.getTupleTypes()) { + if (!isInherentlyImmutableType(tupMemType) && + !isSelectivelyImmutableType(tupMemType, unresolvedTypes)) { return false; } } - if (!recordType.sealed) { - return checkIsJSONType(recordType.restFieldType, unresolvedTypes); + Type tupRestType = tupleType.getRestType(); + if (tupRestType == null) { + return true; } - 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); - return switch (sourceType.getTag()) { - case TypeTags.RECORD_TYPE_TAG -> checkIsRecordType((BRecordType) sourceType, targetType, unresolvedTypes); - case TypeTags.MAP_TAG -> checkIsRecordType((BMapType) sourceType, targetType, unresolvedTypes); - default -> 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()) { - long 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); - return switch (sourceType.getTag()) { - case TypeTags.RECORD_TYPE_TAG -> - checkIsRecordType((MapValue) sourceVal, (BRecordType) sourceType, targetType, unresolvedTypes); - case TypeTags.MAP_TAG -> checkIsRecordType((BMapType) sourceType, targetType, unresolvedTypes); - default -> 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); - return switch (sourceType.getTag()) { - case TypeTags.ERROR_TAG, - TypeTags.READONLY_TAG -> false; - case TypeTags.UNION_TAG, - TypeTags.ANYDATA_TAG, - TypeTags.JSON_TAG -> { - for (Type memberType : ((BUnionType) sourceType).getMemberTypes()) { - if (!checkIsAnyType(memberType)) { - yield false; - } - } - yield true; - } - default -> 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 (!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) { - 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) { - long 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; - } - - return switch (sourceType.getTag()) { - case TypeTags.XML_TEXT_TAG, - TypeTags.FINITE_TYPE_TAG, // Assuming a finite type will only have members from simple basic types. - TypeTags.READONLY_TAG, - TypeTags.NULL_TAG, - TypeTags.NEVER_TAG, - TypeTags.ERROR_TAG, - TypeTags.INVOKABLE_TAG, - TypeTags.SERVICE_TAG, - TypeTags.TYPEDESC_TAG, - TypeTags.FUNCTION_POINTER_TAG, - TypeTags.HANDLE_TAG, - TypeTags.REG_EXP_TYPE_TAG -> true; - case TypeTags.XML_TAG -> ((BXmlType) sourceType).constraint.getTag() == TypeTags.NEVER_TAG; - case TypeTags.TYPE_REFERENCED_TYPE_TAG -> - isInherentlyImmutableType(((BTypeReferenceType) sourceType).getReferredType()); - default -> false; - }; - } - - public static boolean isSelectivelyImmutableType(Type type, Set unresolvedTypes) { - if (!unresolvedTypes.add(type)) { - return true; - } - - switch (type.getTag()) { - case TypeTags.ANY_TAG: - case TypeTags.ANYDATA_TAG: - case TypeTags.JSON_TAG: - case TypeTags.XML_TAG: - case TypeTags.XML_COMMENT_TAG: - case TypeTags.XML_ELEMENT_TAG: - case TypeTags.XML_PI_TAG: - return true; - case TypeTags.ARRAY_TAG: - Type elementType = ((BArrayType) type).getElementType(); - return isInherentlyImmutableType(elementType) || - isSelectivelyImmutableType(elementType, unresolvedTypes); - case TypeTags.TUPLE_TAG: - BTupleType tupleType = (BTupleType) type; - for (Type tupMemType : tupleType.getTupleTypes()) { - if (!isInherentlyImmutableType(tupMemType) && - !isSelectivelyImmutableType(tupMemType, unresolvedTypes)) { - return false; - } - } - - Type tupRestType = tupleType.getRestType(); - if (tupRestType == null) { - return true; - } - - return isInherentlyImmutableType(tupRestType) || - isSelectivelyImmutableType(tupRestType, unresolvedTypes); - case TypeTags.RECORD_TYPE_TAG: - BRecordType recordType = (BRecordType) type; - for (Field field : recordType.getFields().values()) { - Type fieldType = field.getFieldType(); - if (!isInherentlyImmutableType(fieldType) && - !isSelectivelyImmutableType(fieldType, unresolvedTypes)) { - return false; - } - } - - Type recordRestType = recordType.restFieldType; - if (recordRestType == null) { - return true; - } - - return isInherentlyImmutableType(recordRestType) || - isSelectivelyImmutableType(recordRestType, unresolvedTypes); - case TypeTags.OBJECT_TYPE_TAG: - BObjectType objectType = (BObjectType) type; - - if (SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.CLASS) && - !SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.READONLY)) { - return false; - } - - for (Field field : objectType.getFields().values()) { - Type fieldType = field.getFieldType(); - if (!isInherentlyImmutableType(fieldType) && - !isSelectivelyImmutableType(fieldType, unresolvedTypes)) { - return false; - } - } - return true; - case TypeTags.MAP_TAG: - Type constraintType = ((BMapType) type).getConstrainedType(); - return isInherentlyImmutableType(constraintType) || - isSelectivelyImmutableType(constraintType, unresolvedTypes); - case TypeTags.TABLE_TAG: - Type tableConstraintType = ((BTableType) type).getConstrainedType(); - return isInherentlyImmutableType(tableConstraintType) || - isSelectivelyImmutableType(tableConstraintType, unresolvedTypes); - case TypeTags.UNION_TAG: - boolean readonlyIntersectionExists = false; - for (Type memberType : ((BUnionType) type).getMemberTypes()) { - if (isInherentlyImmutableType(memberType) || - isSelectivelyImmutableType(memberType, unresolvedTypes)) { - readonlyIntersectionExists = true; - break; - } - } - return readonlyIntersectionExists; - case TypeTags.INTERSECTION_TAG: - return isSelectivelyImmutableType(((BIntersectionType) type).getEffectiveType(), unresolvedTypes); - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return isSelectivelyImmutableType(((BTypeReferenceType) type).getReferredType(), unresolvedTypes); - default: - return false; - } - } - - private static boolean checkConstraints(Type sourceConstraint, Type targetConstraint, - List unresolvedTypes) { - if (sourceConstraint == null) { - sourceConstraint = TYPE_ANY; - } - - if (targetConstraint == null) { - targetConstraint = TYPE_ANY; - } - - return checkIsType(sourceConstraint, targetConstraint, unresolvedTypes); - } - - private static boolean isMutable(Object value, Type sourceType) { - // All the value types are immutable - sourceType = getImpliedType(sourceType); - if (value == null || sourceType.getTag() < TypeTags.NULL_TAG || - sourceType.getTag() == TypeTags.FINITE_TYPE_TAG) { - return false; - } - - return !((BRefValue) value).isFrozen(); - } - - private static boolean checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(Type type) { - Set visitedTypeSet = new HashSet<>(); - return checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(type, visitedTypeSet); - } - - private static boolean checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(Type type, - Set visitedTypeSet) { - switch (type.getTag()) { - case TypeTags.NEVER_TAG: - return true; - case TypeTags.RECORD_TYPE_TAG: - BRecordType recordType = (BRecordType) type; - visitedTypeSet.add(recordType.getName()); - for (Field field : recordType.getFields().values()) { - // skip check for fields with self referencing type and not required fields. - if ((SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED) || - !SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL)) && - !visitedTypeSet.contains(field.getFieldType()) && - checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(field.getFieldType(), - visitedTypeSet)) { - return true; - } - } - return false; - case TypeTags.TUPLE_TAG: - BTupleType tupleType = (BTupleType) type; - visitedTypeSet.add(tupleType.getName()); - List tupleTypes = tupleType.getTupleTypes(); - for (Type mem : tupleTypes) { - if (!visitedTypeSet.add(mem.getName())) { - continue; - } - if (checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(mem, visitedTypeSet)) { - return true; - } - } - return false; - case TypeTags.ARRAY_TAG: - BArrayType arrayType = (BArrayType) type; - visitedTypeSet.add(arrayType.getName()); - Type elemType = arrayType.getElementType(); - visitedTypeSet.add(elemType.getName()); - return arrayType.getState() != ArrayState.OPEN && - checkIsNeverTypeOrStructureTypeWithARequiredNeverMember(elemType, visitedTypeSet); - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return checkIsNeverTypeOrStructureTypeWithARequiredNeverMember( - ((BTypeReferenceType) type).getReferredType(), visitedTypeSet); - case TypeTags.INTERSECTION_TAG: - return checkIsNeverTypeOrStructureTypeWithARequiredNeverMember( - ((BIntersectionType) type).getEffectiveType(), visitedTypeSet); - default: - return false; - } - } - - /** - * Check whether a given value confirms to a given type. First it checks if the type of the value, and - * if fails then falls back to checking the value. - * - * @param errors list to collect typecast errors - * @param sourceValue Value to check - * @param targetType Target type - * @param unresolvedValues Values that are unresolved so far - * @param allowNumericConversion Flag indicating whether to perform numeric conversions - * @param varName variable name to identify the parent of a record field - * @return True if the value confirms to the provided type. False, otherwise. - */ - private static boolean checkIsLikeType(List errors, Object sourceValue, Type targetType, - List unresolvedValues, - boolean allowNumericConversion, String varName) { - Type sourceType = getType(sourceValue); - if (checkIsType(sourceType, targetType, new ArrayList<>())) { - return true; - } - - return checkIsLikeOnValue(errors, sourceValue, sourceType, targetType, unresolvedValues, allowNumericConversion, - varName); - } - - /** - * Check whether a given value confirms to a given type. Strictly checks the value only, and does not consider the - * type of the value for consideration. - * - * @param errors list to collect typecast errors - * @param sourceValue Value to check - * @param sourceType Type of the value - * @param targetType Target type - * @param unresolvedValues Values that are unresolved so far - * @param allowNumericConversion Flag indicating whether to perform numeric conversions - * @param varName variable name to identify the parent of a record field - * @return True if the value confirms to the provided type. False, otherwise. - */ - private static boolean checkIsLikeOnValue(List errors, Object sourceValue, Type sourceType, Type targetType, - List unresolvedValues, boolean allowNumericConversion, - String varName) { - int sourceTypeTag = sourceType.getTag(); - int targetTypeTag = targetType.getTag(); - - switch (sourceTypeTag) { - case TypeTags.INTERSECTION_TAG: - return checkIsLikeOnValue(errors, sourceValue, ((BIntersectionType) sourceType).getEffectiveType(), - targetTypeTag != TypeTags.INTERSECTION_TAG ? targetType : - ((BIntersectionType) targetType).getEffectiveType(), - unresolvedValues, allowNumericConversion, varName); - case TypeTags.PARAMETERIZED_TYPE_TAG: - if (targetTypeTag != TypeTags.PARAMETERIZED_TYPE_TAG) { - return checkIsLikeOnValue(errors, sourceValue, - ((BParameterizedType) sourceType).getParamValueType(), targetType, unresolvedValues, - allowNumericConversion, varName); - } - return checkIsLikeOnValue(errors, sourceValue, ((BParameterizedType) sourceType).getParamValueType(), - ((BParameterizedType) targetType).getParamValueType(), unresolvedValues, - allowNumericConversion, varName); - default: - break; - } - - return switch (targetTypeTag) { - case TypeTags.READONLY_TAG -> true; - case TypeTags.BYTE_TAG -> { - if (TypeTags.isIntegerTypeTag(sourceTypeTag)) { - yield isByteLiteral(((Number) sourceValue).longValue()); - } - yield allowNumericConversion && TypeConverter.isConvertibleToByte(sourceValue); - } - case TypeTags.INT_TAG -> allowNumericConversion && TypeConverter.isConvertibleToInt(sourceValue); - case TypeTags.SIGNED32_INT_TAG, - TypeTags.SIGNED16_INT_TAG, - TypeTags.SIGNED8_INT_TAG, - TypeTags.UNSIGNED32_INT_TAG, - TypeTags.UNSIGNED16_INT_TAG, - TypeTags.UNSIGNED8_INT_TAG -> { - if (TypeTags.isIntegerTypeTag(sourceTypeTag)) { - yield TypeConverter.isConvertibleToIntSubType(sourceValue, targetType); - } - yield allowNumericConversion && TypeConverter.isConvertibleToIntSubType(sourceValue, targetType); - } - case TypeTags.FLOAT_TAG, - TypeTags.DECIMAL_TAG -> - allowNumericConversion && TypeConverter.isConvertibleToFloatingPointTypes(sourceValue); - case TypeTags.CHAR_STRING_TAG -> TypeConverter.isConvertibleToChar(sourceValue); - case TypeTags.RECORD_TYPE_TAG -> - checkIsLikeRecordType(sourceValue, (BRecordType) targetType, unresolvedValues, - allowNumericConversion, varName, errors); - case TypeTags.TABLE_TAG -> checkIsLikeTableType(sourceValue, (BTableType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.JSON_TAG -> - checkIsLikeJSONType(sourceValue, sourceType, (BJsonType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.MAP_TAG -> - checkIsLikeMapType(sourceValue, (BMapType) targetType, unresolvedValues, allowNumericConversion); - case TypeTags.STREAM_TAG -> checkIsLikeStreamType(sourceValue, (BStreamType) targetType); - case TypeTags.ARRAY_TAG -> checkIsLikeArrayType(sourceValue, (BArrayType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.TUPLE_TAG -> checkIsLikeTupleType(sourceValue, (BTupleType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.ERROR_TAG -> checkIsLikeErrorType(sourceValue, (BErrorType) targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.ANYDATA_TAG -> - checkIsLikeAnydataType(sourceValue, sourceType, unresolvedValues, allowNumericConversion); - case TypeTags.FINITE_TYPE_TAG -> - checkFiniteTypeAssignable(sourceValue, sourceType, (BFiniteType) targetType, - unresolvedValues, allowNumericConversion); - case TypeTags.XML_ELEMENT_TAG, - TypeTags.XML_COMMENT_TAG, - TypeTags.XML_PI_TAG, - TypeTags.XML_TEXT_TAG -> { - if (TypeTags.isXMLTypeTag(sourceTypeTag)) { - yield checkIsLikeXmlValueSingleton((XmlValue) sourceValue, targetType); - } - yield false; - } - case TypeTags.XML_TAG -> { - if (TypeTags.isXMLTypeTag(sourceTypeTag)) { - yield checkIsLikeXMLSequenceType((XmlValue) sourceValue, targetType); - } - yield false; - } - case TypeTags.UNION_TAG -> - checkIsLikeUnionType(errors, sourceValue, (BUnionType) targetType, unresolvedValues, - allowNumericConversion, varName); - case TypeTags.INTERSECTION_TAG -> checkIsLikeOnValue(errors, sourceValue, sourceType, - ((BIntersectionType) targetType).getEffectiveType(), unresolvedValues, allowNumericConversion, - varName); - case TypeTags.TYPE_REFERENCED_TYPE_TAG -> checkIsLikeOnValue(errors, sourceValue, sourceType, - ((BTypeReferenceType) targetType).getReferredType(), unresolvedValues, allowNumericConversion, - varName); - default -> 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) { - return switch (getImpliedType(type).getTag()) { - case TypeTags.XML_ELEMENT_TAG -> XmlNodeType.ELEMENT; - case TypeTags.XML_COMMENT_TAG -> XmlNodeType.COMMENT; - case TypeTags.XML_PI_TAG -> XmlNodeType.PI; - default -> XmlNodeType.TEXT; - }; - } - - private static boolean checkIsLikeXmlValueSingleton(XmlValue xmlSource, Type targetType) { - XmlNodeType targetXmlNodeType = getXmlNodeType(targetType); - XmlNodeType xmlSourceNodeType = xmlSource.getNodeType(); - - if (xmlSourceNodeType == targetXmlNodeType) { - return true; - } - - if (xmlSourceNodeType == XmlNodeType.SEQUENCE) { - XmlSequence seq = (XmlSequence) xmlSource; - return seq.size() == 1 && seq.getChildrenList().get(0).getNodeType() == targetXmlNodeType || - (targetXmlNodeType == XmlNodeType.TEXT && seq.isEmpty()); - } - - return false; - } - - private static void populateTargetXmlNodeTypes(Set nodeTypes, Type targetType) { - // there are only 4 xml subtypes - if (nodeTypes.size() == 4) { - return; - } - - Type referredType = getImpliedType(targetType); - switch (referredType.getTag()) { - case TypeTags.UNION_TAG: - for (Type memberType : ((UnionType) referredType).getMemberTypes()) { - populateTargetXmlNodeTypes(nodeTypes, memberType); - } - break; - case TypeTags.INTERSECTION_TAG: - populateTargetXmlNodeTypes(nodeTypes, ((IntersectionType) referredType).getEffectiveType()); - break; - case TypeTags.XML_ELEMENT_TAG: - nodeTypes.add(XmlNodeType.ELEMENT); - break; - case TypeTags.XML_COMMENT_TAG: - nodeTypes.add(XmlNodeType.COMMENT); - break; - case TypeTags.XML_PI_TAG: - nodeTypes.add(XmlNodeType.PI); - break; - case TypeTags.XML_TEXT_TAG: - nodeTypes.add(XmlNodeType.TEXT); - break; - case TypeTags.XML_TAG: - populateTargetXmlNodeTypes(nodeTypes, ((BXmlType) referredType).constraint); - break; - default: - break; - - } - } - - private static boolean checkIsLikeXMLSequenceType(XmlValue xmlSource, Type targetType) { - Set acceptedNodeTypes = new HashSet<>(); - populateTargetXmlNodeTypes(acceptedNodeTypes, targetType); - - XmlNodeType xmlSourceNodeType = xmlSource.getNodeType(); - if (xmlSourceNodeType != XmlNodeType.SEQUENCE) { - return acceptedNodeTypes.contains(xmlSourceNodeType); - } - - XmlSequence seq = (XmlSequence) xmlSource; - for (BXml m : seq.getChildrenList()) { - if (!acceptedNodeTypes.contains(m.getNodeType())) { - return false; - } - } - return true; - } - - public static boolean isNumericType(Type type) { - type = getImpliedType(type); - return type.getTag() < TypeTags.STRING_TAG || TypeTags.isIntegerTypeTag(type.getTag()); - } - - private static boolean checkIsLikeAnydataType(Object sourceValue, Type sourceType, - List unresolvedValues, - boolean allowNumericConversion) { - sourceType = getImpliedType(sourceType); - switch (sourceType.getTag()) { + return isInherentlyImmutableType(tupRestType) || + isSelectivelyImmutableType(tupRestType, unresolvedTypes); 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()); - return switch (getImpliedType(arrayType.getElementType()).getTag()) { - case TypeTags.INT_TAG, - TypeTags.FLOAT_TAG, - TypeTags.DECIMAL_TAG, - TypeTags.STRING_TAG, - TypeTags.BOOLEAN_TAG, - TypeTags.BYTE_TAG -> true; - default -> 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; - } - - public static boolean isByteLiteral(long longValue) { - return (longValue >= BBYTE_MIN_VALUE && longValue <= BBYTE_MAX_VALUE); - } - - static boolean isSigned32LiteralValue(Long longObject) { - - return (longObject >= SIGNED32_MIN_VALUE && longObject <= SIGNED32_MAX_VALUE); - } - - static boolean isSigned16LiteralValue(Long longObject) { - - return (longObject.intValue() >= SIGNED16_MIN_VALUE && longObject.intValue() <= SIGNED16_MAX_VALUE); - } - - static boolean isSigned8LiteralValue(Long longObject) { - - return (longObject.intValue() >= SIGNED8_MIN_VALUE && longObject.intValue() <= SIGNED8_MAX_VALUE); - } - - static boolean isUnsigned32LiteralValue(Long longObject) { - - return (longObject >= 0 && longObject <= UNSIGNED32_MAX_VALUE); - } - - static boolean isUnsigned16LiteralValue(Long longObject) { - - return (longObject.intValue() >= 0 && longObject.intValue() <= UNSIGNED16_MAX_VALUE); - } - - static boolean isUnsigned8LiteralValue(Long longObject) { - - return (longObject.intValue() >= 0 && longObject.intValue() <= UNSIGNED8_MAX_VALUE); - } - - static boolean isCharLiteralValue(Object object) { - String value; - if (object instanceof BString bString) { - value = bString.getValue(); - } else if (object instanceof String s) { - value = s; - } else { - return false; - } - return value.codePoints().count() == 1; - } - - private static boolean checkIsLikeArrayType(Object sourceValue, BArrayType targetType, - List unresolvedValues, boolean allowNumericConversion) { - if (!(sourceValue instanceof ArrayValue source)) { - return false; - } - - 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) { + BRecordType recordType = (BRecordType) type; + for (Field field : recordType.getFields().values()) { + Type fieldType = field.getFieldType(); + if (!isInherentlyImmutableType(fieldType) && + !isSelectivelyImmutableType(fieldType, unresolvedTypes)) { 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; + Type recordRestType = recordType.restFieldType; + if (recordRestType == null) { + return true; } - } - } - - 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 sourceMapValue)) { - return false; - } - - for (Object mapEntry : sourceMapValue.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 streamValue)) { - return false; - } - - BStreamType streamType = (BStreamType) streamValue.getType(); - - return streamType.getConstrainedType() == targetType.getConstrainedType(); - } + return isInherentlyImmutableType(recordRestType) || + isSelectivelyImmutableType(recordRestType, unresolvedTypes); + case TypeTags.OBJECT_TYPE_TAG: + BObjectType objectType = (BObjectType) type; - 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; + if (SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.CLASS) && + !SymbolFlags.isFlagOn(objectType.flags, SymbolFlags.READONLY)) { + return false; } - 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)) { + 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 checkIsMappingLikeJsonType((MapValueImpl) sourceValue, targetType, unresolvedValues, - allowNumericConversion); - case TypeTags.RECORD_TYPE_TAG: - TypeValuePair typeValuePair = new TypeValuePair(sourceValue, targetType); - if (unresolvedValues.contains(typeValuePair)) { - return true; + Type constraintType = ((MapType) 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; + } } - unresolvedValues.add(typeValuePair); - return checkIsMappingLikeJsonType((MapValueImpl) sourceValue, targetType, 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 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; + static boolean isSigned32LiteralValue(Long longObject) { + + return (longObject >= SIGNED32_MIN_VALUE && longObject <= SIGNED32_MAX_VALUE); } - private static boolean checkIsLikeRecordType(Object sourceValue, BRecordType targetType, - List unresolvedValues, boolean allowNumericConversion, - String varName, List errors) { - if (!(sourceValue instanceof MapValueImpl sourceMapValue)) { - return false; - } + static boolean isSigned16LiteralValue(Long longObject) { - TypeValuePair typeValuePair = new TypeValuePair(sourceValue, targetType); - if (unresolvedValues.contains(typeValuePair)) { - return true; - } - unresolvedValues.add(typeValuePair); + return (longObject.intValue() >= SIGNED16_MIN_VALUE && longObject.intValue() <= SIGNED16_MAX_VALUE); + } - Map targetFieldTypes = new HashMap<>(); - Type restFieldType = targetType.restFieldType; - boolean returnVal = true; + static boolean isSigned8LiteralValue(Long longObject) { - for (Field field : targetType.getFields().values()) { - targetFieldTypes.put(field.getFieldName(), field.getFieldType()); - } + return (longObject.intValue() >= SIGNED8_MIN_VALUE && longObject.intValue() <= SIGNED8_MAX_VALUE); + } - for (Map.Entry targetTypeEntry : targetFieldTypes.entrySet()) { - String fieldName = targetTypeEntry.getKey().toString(); - String fieldNameLong = TypeConverter.getLongFieldName(varName, fieldName); - Field targetField = targetType.getFields().get(fieldName); + static boolean isUnsigned32LiteralValue(Long longObject) { - if (!(sourceMapValue.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; - } - } + return (longObject >= 0 && longObject <= UNSIGNED32_MAX_VALUE); + } - for (Object object : sourceMapValue.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(); + static boolean isUnsigned16LiteralValue(Long longObject) { - 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; + return (longObject.intValue() >= 0 && longObject.intValue() <= UNSIGNED16_MAX_VALUE); } - 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); - } - } + static boolean isUnsigned8LiteralValue(Long longObject) { - private static boolean checkIsLikeTableType(Object sourceValue, BTableType targetType, - List unresolvedValues, boolean allowNumericConversion) { - if (!(sourceValue instanceof TableValueImpl tableValue)) { - return false; - } - BTableType sourceType = (BTableType) getImpliedType(tableValue.getType()); - if (targetType.getKeyType() != null && sourceType.getFieldNames().length == 0) { - return false; - } + return (longObject.intValue() >= 0 && longObject.intValue() <= UNSIGNED8_MAX_VALUE); + } - if (sourceType.getKeyType() != null && !checkIsType(tableValue.getKeyType(), targetType.getKeyType())) { + static boolean isCharLiteralValue(Object object) { + String value; + if (object instanceof BString) { + value = ((BString) object).getValue(); + } else if (object instanceof String) { + value = (String) object; + } else { return false; } + return value.codePoints().count() == 1; + } - TypeValuePair typeValuePair = new TypeValuePair(sourceValue, targetType); - if (unresolvedValues.contains(typeValuePair)) { + /** + * Deep value equality check for anydata. + * + * @param lhsValue The value on the left hand side + * @param rhsValue The value on the right hand side + * @param checkedValues Structured value pairs already compared or being compared + * @return True if values are equal, else false. + */ + public static boolean isEqual(Object lhsValue, Object rhsValue, Set checkedValues) { + if (lhsValue == rhsValue) { return true; } - Object[] objects = tableValue.values().toArray(); - for (Object object : objects) { - if (!checkIsLikeType(object, targetType.getConstrainedType(), allowNumericConversion)) { - return false; - } + if (null == lhsValue || null == rhsValue) { + return false; } - return true; + + return checkValueEqual(lhsValue, rhsValue, new HashSet<>(checkedValues)); } - 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); - } + private static boolean checkValueEqual(Object lhsValue, Object rhsValue, Set checkedValues) { + Context cx = context(); + SemType lhsShape = ShapeAnalyzer.inherentTypeOf(cx, lhsValue).orElseThrow(); + SemType rhsShape = ShapeAnalyzer.inherentTypeOf(cx, rhsValue).orElseThrow(); + Predicate belongToSameBasicType = (basicType) -> Core.containsBasicType(lhsShape, basicType) && + Core.containsBasicType(rhsShape, basicType); + if (belongToSameBasicType.test(Builder.getStringType()) || + belongToSameBasicType.test(Builder.getBooleanType())) { + return lhsValue.equals(rhsValue); + } + if (belongToSameBasicType.test(Builder.getIntType())) { + // TODO: is this correct if one of the values are bytes (shouldn't we check of unsigned etc) + return ((Number) lhsValue).longValue() == ((Number) rhsValue).longValue(); } + if (belongToSameBasicType.test(Builder.getFloatType())) { + Double lhs = (Double) lhsValue; + Double rhs = (Double) rhsValue; + // directly doing equals don't work with -0 and 0 + return (Double.isNaN(lhs) && Double.isNaN(rhs)) || lhs.doubleValue() == rhs.doubleValue(); + } + if (belongToSameBasicType.test(Builder.getDecimalType())) { + return checkDecimalEqual((DecimalValue) lhsValue, (DecimalValue) rhsValue); + } + if (belongToSameBasicType.test(RefValueTypeMaskHolder.REF_TYPE_MASK)) { + RefValue lhs = (RefValue) lhsValue; + return lhs.equals(rhsValue, checkedValues); + } + return false; + } - for (Object valueSpaceItem : targetType.valueSpace) { - // TODO: 8/13/19 Maryam fix for conversion - if (isFiniteTypeValue(sourceValue, sourceType, valueSpaceItem, allowNumericConversion)) { + public static boolean isRegExpType(Type targetType) { + if (targetType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { + Type referredType = ((BTypeReferenceType) targetType).getReferredType(); + Module referredTypePackage = referredType.getPackage(); + if ((referredTypePackage != null) && BALLERINA_BUILTIN_PKG_PREFIX.equals(referredTypePackage.getOrg()) + && REGEXP_LANG_LIB.equals(referredTypePackage.getName()) + && REG_EXP_TYPENAME.equals(referredType.getName())) { return true; } + return isRegExpType(referredType); } return false; } + static boolean isStructuredType(Type type) { + Type referredType = getImpliedType(type); + return switch (referredType.getTag()) { + case TypeTags.ARRAY_TAG, + TypeTags.TUPLE_TAG, + TypeTags.MAP_TAG, + TypeTags.RECORD_TYPE_TAG, + TypeTags.TABLE_TAG -> + true; + default -> false; + }; + } + static boolean isFiniteTypeValue(Object sourceValue, Type sourceType, Object valueSpaceItem, boolean allowNumericConversion) { Type valueSpaceItemType = getType(valueSpaceItem); @@ -2808,7 +918,8 @@ static boolean isFiniteTypeValue(Object sourceValue, Type sourceType, Object val DecimalValue.valueOf(((Number) valueSpaceItem).longValue())) && allowNumericConversion; case TypeTags.FLOAT_TAG: return checkDecimalEqual((DecimalValue) sourceValue, - DecimalValue.valueOf(((Number) valueSpaceItem).doubleValue())) && allowNumericConversion; + DecimalValue.valueOf(((Number) valueSpaceItem).doubleValue())) && + allowNumericConversion; case TypeTags.DECIMAL_TAG: return checkDecimalEqual((DecimalValue) sourceValue, (DecimalValue) valueSpaceItem); } @@ -2820,183 +931,8 @@ static boolean isFiniteTypeValue(Object sourceValue, Type sourceType, Object val } } - 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. - * - * @param lhsValue The value on the left hand side - * @param rhsValue The value on the right hand side - * @param checkedValues Structured value pairs already compared or being compared - * @return True if values are equal, else false. - */ - public static boolean isEqual(Object lhsValue, Object rhsValue, Set checkedValues) { - if (lhsValue == rhsValue) { - return true; - } - - if (null == lhsValue || null == rhsValue) { - return false; - } - - return checkValueEquals(lhsValue, rhsValue, checkedValues, getType(lhsValue), getType(rhsValue)); - } - - private static boolean checkValueEquals(Object lhsValue, Object rhsValue, Set checkedValues, - Type lhsValType, Type rhsValType) { - lhsValType = getImpliedType(lhsValType); - rhsValType = getImpliedType(rhsValType); - int lhsValTypeTag = lhsValType.getTag(); - int rhsValTypeTag = rhsValType.getTag(); - - switch (lhsValTypeTag) { - case TypeTags.STRING_TAG: - case TypeTags.BOOLEAN_TAG: - return lhsValue.equals(rhsValue); - case TypeTags.INT_TAG: - if (rhsValTypeTag != TypeTags.BYTE_TAG && rhsValTypeTag != TypeTags.INT_TAG) { - return false; - } - return lhsValue.equals(((Number) rhsValue).longValue()); - case TypeTags.BYTE_TAG: - if (rhsValTypeTag != TypeTags.BYTE_TAG && rhsValTypeTag != TypeTags.INT_TAG) { - return false; - } - return ((Number) lhsValue).byteValue() == ((Number) rhsValue).byteValue(); - case TypeTags.FLOAT_TAG: - if (rhsValTypeTag != TypeTags.FLOAT_TAG) { - return false; - } - if (Double.isNaN((Double) lhsValue) && Double.isNaN((Double) rhsValue)) { - return true; - } - return ((Number) lhsValue).doubleValue() == ((Number) rhsValue).doubleValue(); - case TypeTags.DECIMAL_TAG: - if (rhsValTypeTag != TypeTags.DECIMAL_TAG) { - return false; - } - return checkDecimalEqual((DecimalValue) lhsValue, (DecimalValue) rhsValue); - case TypeTags.XML_TAG: - // Instance of xml never - if (lhsValue instanceof XmlText xmlText) { - return TypeTags.isXMLTypeTag(rhsValTypeTag) && xmlText.equals(rhsValue, checkedValues); - } - return TypeTags.isXMLTypeTag(rhsValTypeTag) && ((XmlSequence) lhsValue).equals(rhsValue, checkedValues); - case TypeTags.XML_ELEMENT_TAG: - return TypeTags.isXMLTypeTag(rhsValTypeTag) && ((XmlItem) lhsValue).equals(rhsValue, checkedValues); - case TypeTags.XML_COMMENT_TAG: - return TypeTags.isXMLTypeTag(rhsValTypeTag) && ((XmlComment) lhsValue).equals(rhsValue, checkedValues); - case TypeTags.XML_TEXT_TAG: - return TypeTags.isXMLTypeTag(rhsValTypeTag) && ((XmlText) lhsValue).equals(rhsValue, checkedValues); - case TypeTags.XML_PI_TAG: - return TypeTags.isXMLTypeTag(rhsValTypeTag) && ((XmlPi) lhsValue).equals(rhsValue, checkedValues); - case TypeTags.MAP_TAG: - case TypeTags.JSON_TAG: - case TypeTags.RECORD_TYPE_TAG: - return isMappingType(rhsValTypeTag) && ((MapValueImpl) lhsValue).equals(rhsValue, checkedValues); - case TypeTags.TUPLE_TAG: - case TypeTags.ARRAY_TAG: - return isListType(rhsValTypeTag) && ((ArrayValue) lhsValue).equals(rhsValue, checkedValues); - case TypeTags.ERROR_TAG: - return rhsValTypeTag == TypeTags.ERROR_TAG && ((ErrorValue) lhsValue).equals(rhsValue, checkedValues); - case TypeTags.TABLE_TAG: - return rhsValTypeTag == TypeTags.TABLE_TAG && - ((TableValueImpl) lhsValue).equals(rhsValue, checkedValues); - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return checkValueEquals(lhsValue, rhsValue, checkedValues, - ((BTypeReferenceType) lhsValType).getReferredType(), rhsValType); - case TypeTags.SERVICE_TAG: - default: - if (lhsValue instanceof RegExpValue lhsRegExpValue) { - return lhsRegExpValue.equals(rhsValue, checkedValues); - } - return false; - } - } - - private static boolean isListType(int typeTag) { - return typeTag == TypeTags.ARRAY_TAG || typeTag == TypeTags.TUPLE_TAG; - } - - private static boolean isMappingType(int typeTag) { - return typeTag == TypeTags.MAP_TAG || typeTag == TypeTags.RECORD_TYPE_TAG || typeTag == TypeTags.JSON_TAG; - } - - public static boolean isRegExpType(Type targetType) { - if (targetType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { - Type referredType = ((BTypeReferenceType) targetType).getReferredType(); - Module referredTypePackage = referredType.getPackage(); - if ((referredTypePackage != null) && BALLERINA_BUILTIN_PKG_PREFIX.equals(referredTypePackage.getOrg()) - && REGEXP_LANG_LIB.equals(referredTypePackage.getName()) - && REG_EXP_TYPENAME.equals(referredType.getName())) { - return true; - } - return isRegExpType(referredType); - } - return false; - } - - static boolean isStructuredType(Type type) { - Type referredType = getImpliedType(type); - return switch (referredType.getTag()) { - case TypeTags.ARRAY_TAG, - TypeTags.TUPLE_TAG, - TypeTags.MAP_TAG, - TypeTags.RECORD_TYPE_TAG, - TypeTags.TABLE_TAG -> - true; - default -> false; - }; + public static Env getEnv() { + return Env.getInstance(); } /** @@ -3004,7 +940,7 @@ static boolean isStructuredType(Type type) { * * @since 0.995.0 */ - private static class TypePair { + static class TypePair { Type sourceType; Type targetType; @@ -3044,11 +980,35 @@ public static boolean hasFillerValue(Type type) { return hasFillerValue(type, new ArrayList<>()); } + private enum FillerValueResult { + TRUE, FALSE, MAYBE + } + + private static FillerValueResult hasFillerValueSemType(Context cx, SemType type) { + if (Core.containsBasicType(type, Builder.getNilType())) { + return FillerValueResult.TRUE; + } + if (Integer.bitCount(type.all() | type.some()) > 1) { + return FillerValueResult.FALSE; + } + if (type.some() != 0) { + return FillerValueResult.MAYBE; + } + return Core.containsBasicType(type, TopTypesWithFillValueMaskHolder.TOP_TYPES_WITH_ALWAYS_FILLING) ? + FillerValueResult.TRUE : + FillerValueResult.FALSE; + } + private static boolean hasFillerValue(Type type, List unanalyzedTypes) { if (type == null) { return true; } + FillerValueResult fastResult = hasFillerValueSemType(context(), SemType.tryInto(type)); + if (fastResult != FillerValueResult.MAYBE) { + return fastResult == FillerValueResult.TRUE; + } + int typeTag = type.getTag(); if (TypeTags.isXMLTypeTag(typeTag)) { return typeTag == TypeTags.XML_TAG || typeTag == TypeTags.XML_TEXT_TAG; @@ -3077,7 +1037,7 @@ private static boolean hasFillerValue(Type type, List unanalyzedTypes) { }; } - private static boolean checkFillerValue(BTupleType tupleType, List unAnalyzedTypes) { + private static boolean checkFillerValue(BTupleType tupleType, List unAnalyzedTypes) { if (unAnalyzedTypes.contains(tupleType)) { return true; } @@ -3290,6 +1250,96 @@ private static BError createTypeCastError(Object value, Type targetType, List + ErrorUtils.createTypeCastError(inputValue, PredefinedTypes.TYPE_BYTE)); + } + Predicate isIntSubType = (subType) -> Core.isSameType(cx, targetType, SemType.tryInto(subType)); + if (isIntSubType.test(PredefinedTypes.TYPE_INT_SIGNED_32)) { + return anyToSigned32(inputValue); + } + if (isIntSubType.test(PredefinedTypes.TYPE_INT_SIGNED_16)) { + return anyToSigned16(inputValue); + } + if (isIntSubType.test(PredefinedTypes.TYPE_INT_SIGNED_8)) { + return anyToSigned8(inputValue); + } + if (isIntSubType.test(PredefinedTypes.TYPE_INT_UNSIGNED_32)) { + return anyToUnsigned32(inputValue); + } + if (isIntSubType.test(PredefinedTypes.TYPE_INT_UNSIGNED_16)) { + return anyToUnsigned16(inputValue); + } + if (isIntSubType.test(PredefinedTypes.TYPE_INT_UNSIGNED_8)) { + return anyToUnsigned8(inputValue); + } + return anyToIntCast(inputValue, () -> + ErrorUtils.createTypeCastError(inputValue, PredefinedTypes.TYPE_INT)); + } + public static Object castValues(Type targetType, Object inputValue) { - return switch (targetType.getTag()) { - case TypeTags.SIGNED32_INT_TAG -> anyToSigned32(inputValue); - case TypeTags.SIGNED16_INT_TAG -> anyToSigned16(inputValue); - case TypeTags.SIGNED8_INT_TAG -> anyToSigned8(inputValue); - case TypeTags.UNSIGNED32_INT_TAG -> anyToUnsigned32(inputValue); - case TypeTags.UNSIGNED16_INT_TAG -> anyToUnsigned16(inputValue); - case TypeTags.UNSIGNED8_INT_TAG -> anyToUnsigned8(inputValue); - case TypeTags.INT_TAG -> anyToIntCast(inputValue, () -> - ErrorUtils.createTypeCastError(inputValue, PredefinedTypes.TYPE_INT)); - case TypeTags.DECIMAL_TAG -> anyToDecimalCast(inputValue, () -> + return castValuesInner(SemType.tryInto(targetType), inputValue, + () -> ErrorUtils.createTypeCastError(inputValue, targetType)); + } + + static Object castValuesInner(SemType targetType, Object inputValue, Supplier errorSupplier) { + Context cx = TypeChecker.context(); + if (Core.isSubType(cx, targetType, Builder.getIntType())) { + return castValueToInt(targetType, inputValue); + } + if (Core.isSubType(cx, targetType, Builder.getDecimalType())) { + return anyToDecimalCast(inputValue, () -> ErrorUtils.createTypeCastError(inputValue, PredefinedTypes.TYPE_DECIMAL)); - case TypeTags.FLOAT_TAG -> anyToFloatCast(inputValue, () -> + } + if (Core.isSubType(cx, targetType, Builder.getFloatType())) { + return anyToFloatCast(inputValue, () -> ErrorUtils.createTypeCastError(inputValue, PredefinedTypes.TYPE_FLOAT)); - case TypeTags.STRING_TAG -> anyToStringCast(inputValue, () -> + } + if (Core.isSubType(cx, targetType, Builder.getStringType())) { + return anyToStringCast(inputValue, () -> ErrorUtils.createTypeCastError(inputValue, PredefinedTypes.TYPE_STRING)); - case TypeTags.BOOLEAN_TAG -> anyToBooleanCast(inputValue, () -> + } + if (Core.isSubType(cx, targetType, Builder.getBooleanType())) { + return anyToBooleanCast(inputValue, () -> ErrorUtils.createTypeCastError(inputValue, PredefinedTypes.TYPE_BOOLEAN)); - case TypeTags.BYTE_TAG -> anyToByteCast(inputValue, () -> - ErrorUtils.createTypeCastError(inputValue, PredefinedTypes.TYPE_BYTE)); - default -> throw ErrorUtils.createTypeCastError(inputValue, targetType); - }; + } + throw errorSupplier.get(); } static boolean isConvertibleToByte(Object value) { @@ -266,7 +308,7 @@ public static Type getConvertibleType(Object inputValue, Type targetType, String } break; case TypeTags.MAP_TAG: - if (isConvertibleToMapType(inputValue, (BMapType) targetType, unresolvedValues, varName, errors, + if (isConvertibleToMapType(inputValue, (MapType) targetType, unresolvedValues, varName, errors, allowNumericConversion)) { return targetType; } @@ -385,7 +427,7 @@ public static Type getConvertibleFiniteType(Object inputValue, BFiniteType targe // only the first matching type is returned. if (targetFiniteType.valueSpace.size() == 1) { Type valueType = getType(targetFiniteType.valueSpace.iterator().next()); - if (!isSimpleBasicType(valueType) && valueType.getTag() != TypeTags.NULL_TAG) { + if (!belongToSingleBasicTypeOrString(valueType) && valueType.getTag() != TypeTags.NULL_TAG) { return getConvertibleType(inputValue, valueType, varName, unresolvedValues, errors, allowNumericConversion); } @@ -499,7 +541,7 @@ static String getShortSourceValue(Object sourceValue) { return "()"; } String sourceValueName = sourceValue.toString(); - if (TypeChecker.getType(sourceValue) == TYPE_STRING) { + if (TypeChecker.checkIsType(sourceValue, TYPE_STRING)) { sourceValueName = "\"" + sourceValueName + "\""; } if (sourceValueName.length() > MAX_DISPLAYED_SOURCE_VALUE_LENGTH) { @@ -577,7 +619,7 @@ private static boolean isConvertibleToTableType(Object sourceValue, BTableType t } } - private static boolean isConvertibleToMapType(Object sourceValue, BMapType targetType, + private static boolean isConvertibleToMapType(Object sourceValue, MapType targetType, Set unresolvedValues, String varName, List errors, boolean allowNumericConversion) { if (!(sourceValue instanceof MapValueImpl)) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/json/JsonInternalUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/json/JsonInternalUtils.java index 36e0fe5e5f12..e77b4b4aa9fb 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/json/JsonInternalUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/json/JsonInternalUtils.java @@ -43,7 +43,6 @@ import io.ballerina.runtime.internal.types.BArrayType; import io.ballerina.runtime.internal.types.BFiniteType; import io.ballerina.runtime.internal.types.BJsonType; -import io.ballerina.runtime.internal.types.BMapType; import io.ballerina.runtime.internal.types.BStructureType; import io.ballerina.runtime.internal.types.BUnionType; import io.ballerina.runtime.internal.values.ArrayValue; @@ -363,7 +362,7 @@ public static Object convertJSON(Object jsonValue, Type targetType) { case TypeTags.ARRAY_TAG: return convertJSONToBArray(jsonValue, (BArrayType) targetType); case TypeTags.MAP_TAG: - return jsonToMap(jsonValue, (BMapType) targetType); + return jsonToMap(jsonValue, (MapType) targetType); case TypeTags.NULL_TAG: if (jsonValue == null) { return null; 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 feb9d147bf7c..adb22bc0bdde 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,10 @@ import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.RefValue; import java.util.Optional; @@ -34,11 +38,7 @@ * * @since 0.995.0 */ -public class BAnyType extends BType implements AnyType { - - private final boolean readonly; - private IntersectionType immutableType; - private IntersectionType intersectionType = null; +public final class BAnyType extends BSemTypeWrapper implements AnyType { /** * Create a {@code BAnyType} which represents the any type. @@ -46,58 +46,98 @@ public class BAnyType extends BType implements AnyType { * @param typeName string name of the type */ public BAnyType(String typeName, Module pkg, boolean readonly) { - super(typeName, pkg, RefValue.class); - this.readonly = readonly; - - if (!readonly) { - BAnyType immutableAnyType = new BAnyType(TypeConstants.READONLY_ANY_TNAME, pkg, true); - this.immutableType = new BIntersectionType(pkg, new Type[]{ this, PredefinedTypes.TYPE_READONLY}, - immutableAnyType, TypeFlags.asMask(TypeFlags.NILABLE), true); - } + super(new ConcurrentLazySupplier<>(() -> new BAnyTypeImpl(typeName, pkg, readonly)), + typeName, pkg, TypeTags.ANY_TAG, pickSemType(readonly)); } @Override - public V getZeroValue() { - return null; + public Optional getIntersectionType() { + return this.getbType().getIntersectionType(); } @Override - public V getEmptyValue() { - return null; + public void setIntersectionType(IntersectionType intersectionType) { + this.getbType().setIntersectionType(intersectionType); } @Override - public int getTag() { - return TypeTags.ANY_TAG; + public Type getReferredType() { + return this.getbType().getReferredType(); } @Override - public boolean isNilable() { - return true; + public IntersectionType getImmutableType() { + return this.getbType().getImmutableType(); } - @Override - public boolean isReadOnly() { - return this.readonly; - } + protected static final class BAnyTypeImpl extends BType implements AnyType { - @Override - public IntersectionType getImmutableType() { - return this.immutableType; - } + private final boolean readonly; + private IntersectionType immutableType; + private IntersectionType intersectionType = null; - @Override - public void setImmutableType(IntersectionType immutableType) { - this.immutableType = immutableType; - } + private BAnyTypeImpl(String typeName, Module pkg, boolean readonly) { + super(typeName, pkg, RefValue.class); + this.readonly = readonly; + + if (!readonly) { + BAnyType immutableAnyType = new BAnyType(TypeConstants.READONLY_ANY_TNAME, pkg, true); + this.immutableType = new BIntersectionType(pkg, new Type[]{this, PredefinedTypes.TYPE_READONLY}, + immutableAnyType, TypeFlags.asMask(TypeFlags.NILABLE), true); + } + } + + @Override + public V getZeroValue() { + return null; + } + + @Override + public V getEmptyValue() { + return null; + } + + @Override + public int getTag() { + return TypeTags.ANY_TAG; + } + + public boolean isNilable() { + return true; + } + + @Override + public boolean isReadOnly() { + return this.readonly; + } + + @Override + public IntersectionType getImmutableType() { + return this.immutableType; + } + + @Override + public void setImmutableType(IntersectionType immutableType) { + this.immutableType = immutableType; + } + + @Override + public Optional getIntersectionType() { + return this.intersectionType == null ? Optional.empty() : Optional.of(this.intersectionType); + } + + @Override + public void setIntersectionType(IntersectionType intersectionType) { + this.intersectionType = intersectionType; + } - @Override - public Optional getIntersectionType() { - return this.intersectionType == null ? Optional.empty() : Optional.of(this.intersectionType); } - @Override - public void setIntersectionType(IntersectionType intersectionType) { - this.intersectionType = intersectionType; + private static SemType pickSemType(boolean readonly) { + SemType semType = Builder.getAnyType(); + if (readonly) { + semType = Core.intersect(semType, Builder.getReadonlyType()); + } + return semType; } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnydataType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnydataType.java index 0bc38e99f4fb..71dcdc374f48 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnydataType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnydataType.java @@ -24,6 +24,9 @@ import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.RefValue; /** @@ -86,4 +89,16 @@ public String toString() { } return super.toString(); } + + // TODO: this type don't have mutable parts so this should be a immutable + // semtype. But some things could depend on this being a union type descriptor + // as well (which has to be mutable) + @Override + public SemType createSemType() { + SemType semType = Builder.getAnyDataType(); + if (isReadOnly()) { + semType = Core.intersect(semType, Builder.getReadonlyType()); + } + return semType; + } } 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 a399bcddf71a..07834065c87d 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,12 +22,26 @@ import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.DefinitionContainer; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; +import io.ballerina.runtime.internal.values.AbstractArrayValue; 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 java.util.Set; + +import static io.ballerina.runtime.api.types.semtype.Builder.getNeverType; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_LIMITED; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_UNLIMITED; /** * {@code BArrayType} represents a type of an arrays in Ballerina. @@ -40,7 +54,9 @@ * @since 0.995.0 */ @SuppressWarnings("unchecked") -public class BArrayType extends BType implements ArrayType { +public class BArrayType extends BType implements ArrayType, TypeWithShape { + + private static final SemType[] EMPTY_SEMTYPE_ARR = new SemType[0]; private Type elementType; private int dimensions = 1; private int size = -1; @@ -51,6 +67,8 @@ public class BArrayType extends BType implements ArrayType { private IntersectionType immutableType; private IntersectionType intersectionType = null; private int typeFlags; + private final DefinitionContainer defn = new DefinitionContainer<>(); + private final DefinitionContainer acceptedTypeDefn = new DefinitionContainer<>(); public BArrayType(Type elementType) { this(elementType, false); } @@ -85,6 +103,9 @@ public BArrayType(int typeFlags, int size, boolean readonly, boolean hasFillerVa } public void setElementType(Type elementType, int dimensions, boolean elementRO) { + if (this.elementType != null) { + resetSemType(); + } this.elementType = readonly && !elementRO ? ReadOnlyUtils.getReadOnlyType(elementType) : elementType; this.dimensions = dimensions; } @@ -129,7 +150,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { if (obj instanceof BArrayType other) { - if (other.state == ArrayState.CLOSED && this.size != other.size) { + if ((other.state == ArrayState.CLOSED || this.state == ArrayState.CLOSED) && this.size != other.size) { return false; } return this.elementType.equals(other.elementType) && this.readonly == other.readonly; @@ -204,4 +225,101 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + public SemType createSemType() { + Env env = Env.getInstance(); + if (defn.isDefinitionReady()) { + return defn.getSemType(env); + } + var result = defn.trySetDefinition(ListDefinition::new); + if (!result.updated()) { + return defn.getSemType(env); + } + ListDefinition ld = result.definition(); + CellAtomicType.CellMutability mut = isReadOnly() ? CellAtomicType.CellMutability.CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + return getSemTypePart(env, ld, size, tryInto(getElementType()), mut); + } + + private SemType getSemTypePart(Env env, ListDefinition defn, int size, SemType elementType, + CellAtomicType.CellMutability mut) { + if (size == -1) { + return defn.defineListTypeWrapped(env, EMPTY_SEMTYPE_ARR, 0, elementType, mut); + } else { + SemType[] initial = {elementType}; + return defn.defineListTypeWrapped(env, initial, size, getNeverType(), mut); + } + } + + @Override + public void resetSemType() { + defn.clear(); + super.resetSemType(); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return elementType instanceof MayBeDependentType eType && eType.isDependentlyTyped(visited); + } + + @Override + public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + if (!couldInherentTypeBeDifferent()) { + return Optional.of(getSemType()); + } + AbstractArrayValue value = (AbstractArrayValue) object; + SemType cachedShape = value.shapeOf(); + if (cachedShape != null) { + return Optional.of(cachedShape); + } + SemType semType = shapeOfInner(cx, shapeSupplier, value); + value.cacheShape(semType); + return Optional.of(semType); + } + + @Override + public boolean couldInherentTypeBeDifferent() { + return isReadOnly(); + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + return Optional.of(shapeOfInner(cx, shapeSupplier, (AbstractArrayValue) object)); + } + + @Override + public Optional acceptedTypeOf(Context cx) { + Env env = cx.env; + if (acceptedTypeDefn.isDefinitionReady()) { + return Optional.of(acceptedTypeDefn.getSemType(cx.env)); + } + var result = acceptedTypeDefn.trySetDefinition(ListDefinition::new); + if (!result.updated()) { + return Optional.of(acceptedTypeDefn.getSemType(env)); + } + ListDefinition ld = result.definition(); + SemType elementType = ShapeAnalyzer.acceptedTypeOf(cx, getElementType()).orElseThrow(); + return Optional.of(getSemTypePart(env, ld, size, elementType, CELL_MUT_UNLIMITED)); + } + + private SemType shapeOfInner(Context cx, ShapeSupplier shapeSupplier, AbstractArrayValue value) { + ListDefinition readonlyShapeDefinition = value.getReadonlyShapeDefinition(); + if (readonlyShapeDefinition != null) { + return readonlyShapeDefinition.getSemType(cx.env); + } + int size = value.size(); + SemType[] memberTypes = new SemType[size]; + ListDefinition ld = new ListDefinition(); + value.setReadonlyShapeDefinition(ld); + for (int i = 0; i < size; i++) { + Optional memberType = shapeSupplier.get(cx, value.get(i)); + assert memberType.isPresent(); + memberTypes[i] = memberType.get(); + } + CellAtomicType.CellMutability mut = isReadOnly() ? CELL_MUT_NONE : CELL_MUT_LIMITED; + SemType semType = ld.defineListTypeWrapped(cx.env, memberTypes, memberTypes.length, getNeverType(), mut); + value.resetReadonlyShapeDefinition(); + return 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 6fd5b0afb07d..f5a6c275ccda 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,29 @@ package io.ballerina.runtime.internal.types; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.BooleanType; +import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.function.Supplier; /** * {@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), + TypeConstants.BOOLEAN_TNAME, PredefinedTypes.EMPTY_MODULE, Builder.getBooleanConst(true)); + private static final BBooleanType FALSE = + new BBooleanType(() -> new BBooleanTypeImpl(TypeConstants.BOOLEAN_TNAME, PredefinedTypes.EMPTY_MODULE), + TypeConstants.BOOLEAN_TNAME, PredefinedTypes.EMPTY_MODULE, Builder.getBooleanConst(false)); /** * Create a {@code BBooleanType} which represents the boolean type. @@ -34,28 +48,43 @@ 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), typeName, pkg, Builder.getBooleanType()); } - @Override - @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(Supplier bTypeSupplier, String typeName, Module pkg, SemType semType) { + super(new ConcurrentLazySupplier<>(bTypeSupplier), typeName, pkg, TypeTags.BOOLEAN_TAG, semType); } - @Override - public int getTag() { - return TypeTags.BOOLEAN_TAG; - } + protected static final class BBooleanTypeImpl extends BType implements BooleanType { + + private BBooleanTypeImpl(String typeName, Module pkg) { + super(typeName, pkg, Boolean.class); + } + + @Override + @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 97aca3f82894..d69b43dc9b8d 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 @@ -19,15 +19,26 @@ package io.ballerina.runtime.internal.types; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.ByteType; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.function.Supplier; + +import static io.ballerina.runtime.api.constants.RuntimeConstants.UNSIGNED8_MAX_VALUE; +import static io.ballerina.runtime.api.types.PredefinedTypes.EMPTY_MODULE; /** * {@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 +46,50 @@ 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), typeName, EMPTY_MODULE, + Builder.createIntRange(0, UNSIGNED8_MAX_VALUE)); } - @Override - @SuppressWarnings("unchecked") - public V getZeroValue() { - return (V) new Integer(0); + private BByteType(Supplier bTypeSupplier, String typeName, Module pkg, SemType semType) { + super(new ConcurrentLazySupplier<>(bTypeSupplier), typeName, pkg, TypeTags.BYTE_TAG, semType); } - @Override - @SuppressWarnings("unchecked") - public V getEmptyValue() { - return (V) new Integer(0); + public static BByteType singletonType(long value) { + return new BByteType(() -> (BByteTypeImpl) DEFAULT_B_TYPE.clone(), TypeConstants.BYTE_TNAME, EMPTY_MODULE, + Builder.getIntConst(value)); } - @Override - public int getTag() { - return TypeTags.BYTE_TAG; - } + protected 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 + public BType clone() { + return super.clone(); + } } } 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 d1bf252d0f0b..441da73583d2 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 @@ -19,11 +19,18 @@ package io.ballerina.runtime.internal.types; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.DecimalType; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.DecimalValue; import java.math.BigDecimal; +import java.util.function.Supplier; + +import static io.ballerina.runtime.api.types.PredefinedTypes.EMPTY_MODULE; /** * {@code BDecimalType} represents decimal type in Ballerina. @@ -31,7 +38,10 @@ * * @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 +49,49 @@ 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), typeName, pkg, Builder.getDecimalType()); } - @Override - @SuppressWarnings("unchecked") - public V getZeroValue() { - return (V) new DecimalValue(BigDecimal.ZERO); + public static BDecimalType singletonType(BigDecimal value) { + return new BDecimalType(() -> (BDecimalTypeImpl) DEFAULT_B_TYPE.clone(), TypeConstants.DECIMAL_TNAME, + EMPTY_MODULE, Builder.getDecimalConst(value)); } - @Override - @SuppressWarnings("unchecked") - public V getEmptyValue() { - return (V) new DecimalValue(BigDecimal.ZERO); + private BDecimalType(Supplier bType, String typeName, Module pkg, SemType semType) { + super(new ConcurrentLazySupplier<>(bType), typeName, pkg, TypeTags.DECIMAL_TAG, semType); } - @Override - public int getTag() { - return TypeTags.DECIMAL_TAG; - } + protected 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 + public BType clone() { + return super.clone(); + } } } 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 8c1002def149..44e8d987ccbf 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,30 @@ import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +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.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.ErrorUtils; import io.ballerina.runtime.internal.values.ErrorValue; +import io.ballerina.runtime.internal.values.MapValueImpl; import java.util.Optional; +import java.util.Set; /** * {@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, TypeWithShape { - public Type detailType = PredefinedTypes.TYPE_ERROR_DETAIL; + public Type detailType = PredefinedTypes.TYPE_DETAIL; public BTypeIdSet typeIdSet; private IntersectionType intersectionType = null; + private volatile DistinctIdSupplier distinctIdSupplier; public BErrorType(String typeName, Module pkg, Type detailType) { super(typeName, pkg, ErrorValue.class); @@ -50,6 +60,9 @@ public BErrorType(String typeName, Module pkg) { public void setTypeIdSet(BTypeIdSet typeIdSet) { this.typeIdSet = typeIdSet; + synchronized (this) { + this.distinctIdSupplier = null; + } } @Override @@ -113,4 +126,76 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + public SemType createSemType() { + SemType err; + if (detailType == null || isTopType()) { + err = Builder.getErrorType(); + } else { + err = ErrorUtils.errorDetail(tryInto(getDetailType())); + } + + initializeDistinctIdSupplierIfNeeded(); + return distinctIdSupplier.get().stream().map(ErrorUtils::errorDistinct).reduce(err, Core::intersect); + } + + private void initializeDistinctIdSupplierIfNeeded() { + if (distinctIdSupplier == null) { + synchronized (this) { + if (distinctIdSupplier == null) { + distinctIdSupplier = new DistinctIdSupplier(TypeChecker.context().env, getTypeIdSet()); + } + } + } + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return detailType instanceof MayBeDependentType mayBeDependentType && + mayBeDependentType.isDependentlyTyped(visited); + } + + private boolean isTopType() { + return detailType == PredefinedTypes.TYPE_DETAIL; + } + + @Override + public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + if (!couldInherentTypeBeDifferent()) { + return Optional.of(getSemType()); + } + BError errorValue = (BError) object; + Object details = errorValue.getDetails(); + if (!(details instanceof MapValueImpl errorDetails)) { + return Optional.empty(); + } + initializeDistinctIdSupplierIfNeeded(); + // Should we actually pass the readonly shape supplier here? + return BMapType.shapeOfInner(cx, shapeSupplier, errorDetails) + .map(ErrorUtils::errorDetail) + .map(err -> distinctIdSupplier.get().stream().map(ErrorUtils::errorDistinct) + .reduce(err, Core::intersect)); + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Object object) { + BError errorValue = (BError) object; + Object details = errorValue.getDetails(); + if (!(details instanceof MapValueImpl errorDetails)) { + return Optional.empty(); + } + return BMapType.shapeOfInner(cx, shapeSupplierFn, errorDetails).map(ErrorUtils::errorDetail); + } + + @Override + public Optional acceptedTypeOf(Context cx) { + return Optional.of(getSemType()); + } + + @Override + public boolean couldInherentTypeBeDifferent() { + // TODO: consider properly handling this + return true; + } } 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 de03a587aa95..bcef259ed050 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,11 +21,17 @@ import io.ballerina.runtime.api.flags.TypeFlags; import io.ballerina.runtime.api.types.FiniteType; import io.ballerina.runtime.api.types.TypeTags; +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.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.values.RefValue; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.Optional; import java.util.Set; import java.util.StringJoiner; @@ -39,8 +45,8 @@ public class BFiniteType extends BType implements FiniteType { public Set valueSpace; - private final int typeFlags; - private final String originalName; + private int typeFlags; + private String originalName; public BFiniteType(String typeName) { this(typeName, new LinkedHashSet<>(), 0); @@ -187,6 +193,27 @@ public boolean equals(Object o) { if (!(o instanceof BFiniteType that)) { return false; } - return this.valueSpace.size() == that.valueSpace.size() && this.valueSpace.containsAll(that.valueSpace); + if (this.valueSpace.size() != that.valueSpace.size()) { + return false; + } + for (var each : this.valueSpace) { + try { + if (!that.valueSpace.contains(each)) { + return false; + } + } catch (NullPointerException ex) { + // If one of the sets is an immutable collection this can happen + return false; + } + } + return true; + } + + @Override + public SemType createSemType() { + Context cx = TypeChecker.context(); + return this.valueSpace.stream().map(each -> ShapeAnalyzer.inherentTypeOf(cx, each)) + .map(Optional::orElseThrow) + .reduce(Builder.getNeverType(), Core::union); } } 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 50450df52beb..7d5aab69a070 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 @@ -18,8 +18,16 @@ package io.ballerina.runtime.internal.types; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.FloatType; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.function.Supplier; + +import static io.ballerina.runtime.api.types.PredefinedTypes.EMPTY_MODULE; /** * {@code BFloatType} represents a integer which is a 32-bit floating-point number according to the @@ -28,7 +36,7 @@ * @since 0.995.0 */ @SuppressWarnings("unchecked") -public class BFloatType extends BType implements FloatType { +public final class BFloatType extends BSemTypeWrapper implements FloatType { /** * Create a {@code BFloatType} which represents the boolean type. @@ -36,26 +44,42 @@ 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), typeName, pkg, Builder.getFloatType()); } - @Override - public V getZeroValue() { - return (V) new Double(0); - } - - @Override - public V getEmptyValue() { - return (V) new Double(0); + private BFloatType(Supplier bType, String typeName, Module pkg, SemType semType) { + super(new ConcurrentLazySupplier<>(bType), typeName, pkg, TypeTags.FLOAT_TAG, 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), + TypeConstants.FLOAT_TNAME, EMPTY_MODULE, Builder.getFloatConst(value)); } - @Override - public boolean isReadOnly() { - return true; + protected 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 dbef43081922..ecc2a44c1f98 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,8 +25,18 @@ import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.DefinitionContainer; +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; +import java.util.Objects; +import java.util.Set; /** * {@code {@link BFunctionType }} represents a function type in ballerina. @@ -39,6 +49,10 @@ public class BFunctionType extends BAnnotatableType implements FunctionType { 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 final DefinitionContainer defn = new DefinitionContainer<>(); public BFunctionType(Module pkg) { super("function ()", pkg, Object.class); @@ -120,31 +134,14 @@ public boolean equals(Object o) { return false; } - boolean isSourceAnyFunction = SymbolFlags.isFlagOn(this.flags, SymbolFlags.ANY_FUNCTION); - boolean isTargetAnyFunction = SymbolFlags.isFlagOn(that.flags, SymbolFlags.ANY_FUNCTION); - - if (isSourceAnyFunction && isTargetAnyFunction) { - return true; - } - - if (isSourceAnyFunction != isTargetAnyFunction) { - return false; - } - - if (SymbolFlags.isFlagOn(that.flags, SymbolFlags.ISOLATED) != SymbolFlags - .isFlagOn(this.flags, SymbolFlags.ISOLATED)) { - return false; - } - - if (SymbolFlags.isFlagOn(that.flags, SymbolFlags.TRANSACTIONAL) != SymbolFlags - .isFlagOn(this.flags, SymbolFlags.TRANSACTIONAL)) { + if (this.flags != that.flags) { return false; } if (!Arrays.equals(parameters, that.parameters)) { return false; } - return retType.equals(that.retType); + return Objects.equals(retType, that.retType) && Objects.equals(restType, that.restType); } @Override @@ -218,4 +215,96 @@ public Type getReturnType() { public long getFlags() { return flags; } + + private static SemType createIsolatedTop(Env env) { + FunctionDefinition fd = new FunctionDefinition(); + SemType ret = Builder.getValType(); + return fd.define(env, Builder.getNeverType(), ret, FunctionQualifiers.create(true, false)); + } + + @Override + public SemType createSemType() { + if (isFunctionTop()) { + return getTopType(); + } + if (defn.isDefinitionReady()) { + return defn.getSemType(env); + } + var result = defn.trySetDefinition(FunctionDefinition::new); + if (!result.updated()) { + return defn.getSemType(env); + } + FunctionDefinition fd = result.definition(); + SemType[] params = new SemType[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + params[i] = getSemType(parameters[i].type); + } + SemType rest; + if (restType instanceof BArrayType arrayType) { + rest = getSemType(arrayType.getElementType()); + } else { + rest = Builder.getNeverType(); + } + + SemType returnType = resolveReturnType(); + ListDefinition paramListDefinition = new ListDefinition(); + SemType paramType = paramListDefinition.defineListTypeWrapped(env, params, params.length, rest, + CellAtomicType.CellMutability.CELL_MUT_NONE); + return fd.define(env, paramType, returnType, getQualifiers()); + } + + private SemType getTopType() { + if (SymbolFlags.isFlagOn(flags, SymbolFlags.ISOLATED)) { + return ISOLATED_TOP; + } + return Builder.getFunctionType(); + } + + FunctionQualifiers getQualifiers() { + return FunctionQualifiers.create(SymbolFlags.isFlagOn(flags, SymbolFlags.ISOLATED), + SymbolFlags.isFlagOn(flags, SymbolFlags.TRANSACTIONAL)); + } + + private SemType getSemType(Type type) { + return tryInto(type); + } + + private boolean isFunctionTop() { + return parameters == null && restType == null && retType == null; + } + + @Override + public synchronized void resetSemType() { + defn.clear(); + super.resetSemType(); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return (restType instanceof BType rest && rest.isDependentlyTyped(visited)) || + (retType instanceof BType ret && ret.isDependentlyTyped(visited)) || + isDependentlyTypeParameters(visited); + } + + private boolean isDependentlyTypeParameters(Set visited) { + if (parameters == null) { + return false; + } + return Arrays.stream(parameters).map(each -> each.type).filter(each -> each instanceof MayBeDependentType) + .anyMatch(each -> ((MayBeDependentType) each).isDependentlyTyped(visited)); + } + + private SemType resolveReturnType() { + if (retType == null) { + return Builder.getNilType(); + } + MayBeDependentType retBType = (MayBeDependentType) retType; + SemType returnType = getSemType(retType); + ListDefinition ld = new ListDefinition(); + SemType dependentlyTypedBit = + retBType.isDependentlyTyped() ? Builder.getBooleanConst(true) : Builder.getBooleanType(); + SemType[] innerType = new SemType[]{dependentlyTypedBit, returnType}; + return ld.defineListTypeWrapped(env, innerType, 2, Builder.getNeverType(), + CellAtomicType.CellMutability.CELL_MUT_NONE); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFutureType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFutureType.java index 3eb490648562..8839a92922fe 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFutureType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFutureType.java @@ -22,7 +22,13 @@ import io.ballerina.runtime.api.types.FutureType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.FutureUtils; + +import java.util.Objects; +import java.util.Set; /** * {@code BFutureType} represents a future value in Ballerina. @@ -31,7 +37,7 @@ */ public class BFutureType extends BType implements FutureType { - private Type constraint; + private final Type constraint; /** * Create a {@code {@link BFutureType}} which represents the future value. @@ -41,6 +47,7 @@ public class BFutureType extends BType implements FutureType { */ public BFutureType(String typeName, Module pkg) { super(typeName, pkg, Object.class); + constraint = null; } public BFutureType(Type constraint) { @@ -81,8 +88,7 @@ public boolean equals(Object obj) { if (constraint == other.constraint) { return true; } - - return TypeChecker.checkIsType(constraint, other.constraint); + return Objects.equals(constraint, other.constraint); } @Override @@ -93,4 +99,17 @@ public String toString() { private String getConstraintString() { return constraint != null ? "<" + constraint + ">" : ""; } + + @Override + public SemType createSemType() { + if (constraint == null) { + return Builder.getFutureType(); + } + return FutureUtils.futureContaining(TypeChecker.context().env, tryInto(constraint)); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return constraint instanceof MayBeDependentType constraintType && constraintType.isDependentlyTyped(visited); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BHandleType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BHandleType.java index ed1065c1df9e..9bd32efe9577 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BHandleType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BHandleType.java @@ -18,44 +18,68 @@ package io.ballerina.runtime.internal.types; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.HandleType; +import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; import io.ballerina.runtime.internal.values.RefValue; /** - * {@code BHandleType} represents a handle type in Ballerina. - * A handle value is a reference to a storage managed externally by a Ballerina program. + * {@code BHandleType} represents a handle type in Ballerina. A handle value is a reference to a storage managed + * externally by a Ballerina program. * * @since 1.0.0 */ -public class BHandleType extends BType implements HandleType { +public final class BHandleType extends BSemTypeWrapper implements HandleType { /** - * Create a {@code BAnyType} which represents the any type. + * Create a {@code BHandleType} which represents the handle type. * * @param typeName string name of the type */ public BHandleType(String typeName, Module pkg) { - super(typeName, pkg, RefValue.class); + super(new ConcurrentLazySupplier<> + (() -> BHandleTypeImpl.create(typeName, pkg)), typeName, pkg, TypeTags.HANDLE_TAG, + Builder.getHandleType()); } - @Override - public V getZeroValue() { - return null; - } + protected static final class BHandleTypeImpl extends BType implements HandleType { - @Override - public V getEmptyValue() { - return null; - } + private static final BHandleTypeImpl DEFAULT = + new BHandleTypeImpl(TypeConstants.HANDLE_TNAME, PredefinedTypes.EMPTY_MODULE); - @Override - public int getTag() { - return TypeTags.HANDLE_TAG; - } + private static BHandleTypeImpl create(String typeName, Module pkg) { + if (typeName.equals(TypeConstants.HANDLE_TNAME) && pkg == PredefinedTypes.EMPTY_MODULE) { + return DEFAULT; + } + return new BHandleTypeImpl(typeName, pkg); + } + + private BHandleTypeImpl(String typeName, Module pkg) { + super(typeName, pkg, RefValue.class); + } + + @Override + public V getZeroValue() { + return null; + } + + @Override + public V getEmptyValue() { + return null; + } + + @Override + public int getTag() { + return TypeTags.HANDLE_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/BIntegerType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntegerType.java index d8ec02af768b..0b7053f3c0d0 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,42 @@ /* -* 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.constants.TypeConstants; import io.ballerina.runtime.api.types.IntegerType; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.function.Supplier; + +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.PredefinedTypes.EMPTY_MODULE; /** * {@code BIntegerType} represents an integer which is a 32-bit signed number. @@ -27,9 +44,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, EMPTY_MODULE, TypeTags.INT_TAG); /** * Create a {@code BIntegerType} which represents the boolean type. @@ -37,32 +55,89 @@ 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), typeName, pkg, TypeTags.INT_TAG, + Builder.getIntType()); } public BIntegerType(String typeName, Module pkg, int tag) { - super(typeName, pkg, Long.class); - this.tag = tag; + this(() -> new BIntegerTypeImpl(typeName, pkg, tag), typeName, pkg, tag, pickSemType(tag)); + } + + private BIntegerType(Supplier bIntegerTypeSupplier, String typeName, Module pkg, int tag, + SemType semType) { + super(new ConcurrentLazySupplier<>(bIntegerTypeSupplier), typeName, pkg, tag, semType); + } + + private static SemType pickSemType(int tag) { + return switch (tag) { + case TypeTags.INT_TAG -> Builder.getIntType(); + case TypeTags.SIGNED8_INT_TAG -> Builder.createIntRange(SIGNED8_MIN_VALUE, SIGNED8_MAX_VALUE); + case TypeTags.SIGNED16_INT_TAG -> Builder.createIntRange(SIGNED16_MIN_VALUE, SIGNED16_MAX_VALUE); + case TypeTags.SIGNED32_INT_TAG -> Builder.createIntRange(SIGNED32_MIN_VALUE, SIGNED32_MAX_VALUE); + case TypeTags.UNSIGNED8_INT_TAG, TypeTags.BYTE_TAG -> Builder.createIntRange(0, UNSIGNED8_MAX_VALUE); + case TypeTags.UNSIGNED16_INT_TAG -> Builder.createIntRange(0, UNSIGNED16_MAX_VALUE); + case TypeTags.UNSIGNED32_INT_TAG -> Builder.createIntRange(0, UNSIGNED32_MAX_VALUE); + default -> throw new UnsupportedOperationException("Unexpected int tag"); + }; } - @Override - public V getZeroValue() { - return (V) new Long(0); + 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 V getEmptyValue() { - return (V) new Long(0); + private static BIntegerType createSingletonType(long value) { + return new BIntegerType(() -> (BIntegerTypeImpl) DEFAULT_B_TYPE.clone(), TypeConstants.INT_TNAME, EMPTY_MODULE, + TypeTags.INT_TAG, Builder.getIntConst(value)); } - @Override - public int getTag() { - return tag; + protected 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 + public BType clone() { + return super.clone(); + } } - @Override - public boolean isReadOnly() { - return true; + 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 f9cf98a079df..dc1991feb42c 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,20 +24,32 @@ import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +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.types.semtype.ShapeAnalyzer; +import io.ballerina.runtime.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.ErrorUtils; +import io.ballerina.runtime.internal.types.semtype.ObjectDefinition; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.StringJoiner; +import java.util.function.Function; + +import static io.ballerina.runtime.api.utils.TypeUtils.getImpliedType; /** * {@code BIntersectionType} represents an intersection type in Ballerina. * * @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 +227,71 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + public SemType createSemType() { + return createSemTypeInner(SemType::tryInto); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return constituentTypes.stream().filter(each -> each instanceof MayBeDependentType) + .anyMatch(type -> ((MayBeDependentType) type).isDependentlyTyped(visited)); + } + + private SemType createSemTypeInner(Function semTypeFunction) { + if (constituentTypes.isEmpty()) { + return Builder.getNeverType(); + } + SemType result = constituentTypes.stream().map(semTypeFunction).reduce(Core::intersect).orElseThrow(); + Optional distinctPart = distinctTypePart(result); + if (distinctPart.isPresent()) { + result = Core.intersect(result, distinctPart.get()); + } + return result; + } + + private Optional distinctTypePart(SemType result) { + if (Core.isSubtypeSimple(result, Builder.getErrorType())) { + BErrorType effectiveErrorType = (BErrorType) getImpliedType(effectiveType); + DistinctIdSupplier distinctIdSupplier = + new DistinctIdSupplier(TypeChecker.context().env, effectiveErrorType.getTypeIdSet()); + return distinctIdSupplier.get().stream().map(ErrorUtils::errorDistinct).reduce(Core::intersect); + } else if (Core.isSubtypeSimple(result, Builder.getObjectType())) { + BObjectType effectiveObjectType = (BObjectType) getImpliedType(effectiveType); + DistinctIdSupplier distinctIdSupplier = + new DistinctIdSupplier(TypeChecker.context().env, effectiveObjectType.getTypeIdSet()); + return distinctIdSupplier.get().stream().map(ObjectDefinition::distinct).reduce(Core::intersect); + } + return Optional.empty(); + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Object object) { + Type effectiveType = getEffectiveType(); + if (effectiveType instanceof TypeWithShape typeWithShape) { + return typeWithShape.shapeOf(cx, shapeSupplierFn, object); + } + return Optional.empty(); + } + + @Override + public Optional acceptedTypeOf(Context cx) { + return Optional.of(createSemTypeInner(type -> ShapeAnalyzer.acceptedTypeOf(cx, type).orElseThrow())); + } + + @Override + public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + Type effectiveType = getEffectiveType(); + if (effectiveType instanceof TypeWithShape typeWithShape) { + return typeWithShape.inherentTypeOf(cx, shapeSupplier, object); + } + return Optional.empty(); + } + + @Override + public boolean couldInherentTypeBeDifferent() { + return true; + } + } 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 fa47a26e06fc..43d2058cd4d9 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,11 +24,24 @@ import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.DefinitionContainer; +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 java.util.Set; + +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_UNLIMITED; /** * {@code BMapType} represents a type of a map in Ballerina. @@ -41,12 +54,15 @@ * @since 0.995.0 */ @SuppressWarnings("unchecked") -public class BMapType extends BType implements MapType { +public class BMapType extends BType implements MapType, TypeWithShape, Cloneable { + 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 final DefinitionContainer defn = new DefinitionContainer<>(); + private final DefinitionContainer acceptedTypeDefn = new DefinitionContainer<>(); public BMapType(Type constraint) { this(constraint, false); @@ -169,4 +185,104 @@ public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + @Override + public SemType createSemType() { + Env env = Env.getInstance(); + if (defn.isDefinitionReady()) { + return defn.getSemType(env); + } + var result = defn.trySetDefinition(MappingDefinition::new); + if (!result.updated()) { + return defn.getSemType(env); + } + MappingDefinition md = result.definition(); + CellAtomicType.CellMutability mut = isReadOnly() ? CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + return createSemTypeInner(env, md, tryInto(getConstrainedType()), mut); + } + + @Override + public void resetSemType() { + defn.clear(); + super.resetSemType(); + } + + @Override + public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + if (!couldInherentTypeBeDifferent()) { + return Optional.of(getSemType()); + } + MapValueImpl value = (MapValueImpl) object; + SemType cachedShape = value.shapeOf(); + if (cachedShape != null) { + return Optional.of(cachedShape); + } + + return shapeOfInner(cx, shapeSupplier, value); + } + + @Override + public boolean couldInherentTypeBeDifferent() { + return isReadOnly(); + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Object object) { + return shapeOfInner(cx, shapeSupplierFn, (MapValueImpl) object); + } + + @Override + public synchronized Optional acceptedTypeOf(Context cx) { + Env env = cx.env; + if (acceptedTypeDefn.isDefinitionReady()) { + return Optional.of(acceptedTypeDefn.getSemType(env)); + } + var result = acceptedTypeDefn.trySetDefinition(MappingDefinition::new); + if (!result.updated()) { + return Optional.of(acceptedTypeDefn.getSemType(env)); + } + MappingDefinition md = result.definition(); + SemType elementType = ShapeAnalyzer.acceptedTypeOf(cx, getConstrainedType()).orElseThrow(); + return Optional.of(createSemTypeInner(env, md, elementType, CELL_MUT_UNLIMITED)); + } + + static Optional shapeOfInner(Context cx, ShapeSupplier shapeSupplier, MapValueImpl value) { + MappingDefinition readonlyShapeDefinition = value.getReadonlyShapeDefinition(); + if (readonlyShapeDefinition != null) { + return Optional.of(readonlyShapeDefinition.getSemType(cx.env)); + } + int nFields = value.size(); + MappingDefinition md = new MappingDefinition(); + value.setReadonlyShapeDefinition(md); + MappingDefinition.Field[] fields = new MappingDefinition.Field[nFields]; + Map.Entry[] entries = value.entrySet().toArray(Map.Entry[]::new); + for (int i = 0; i < nFields; i++) { + Optional valueType = shapeSupplier.get(cx, entries[i].getValue()); + SemType fieldType = valueType.orElseThrow(); + fields[i] = new MappingDefinition.Field(entries[i].getKey().toString(), fieldType, true, false); + } + CellAtomicType.CellMutability mut = value.getType().isReadOnly() ? CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + SemType semType = md.defineMappingTypeWrapped(cx.env, fields, Builder.getNeverType(), mut); + value.cacheShape(semType); + value.resetReadonlyShapeDefinition(); + return Optional.of(semType); + } + + private SemType createSemTypeInner(Env env, MappingDefinition defn, SemType restType, + CellAtomicType.CellMutability mut) { + return defn.defineMappingTypeWrapped(env, EMPTY_FIELD_ARR, restType, mut); + } + + @Override + public BMapType clone() { + BMapType clone = (BMapType) super.clone(); + clone.defn.clear(); + return clone; + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return constraint instanceof MayBeDependentType constraintType && constraintType.isDependentlyTyped(visited); + } } 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 46d25842338c..4332e1a2f49b 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 @@ -84,4 +84,8 @@ public MethodType duplicate() { public boolean isIsolated() { return SymbolFlags.isFlagOn(flags, SymbolFlags.ISOLATED); } + + 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 c289e0bffd88..5478813f2dc3 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 @@ -24,6 +24,9 @@ import io.ballerina.runtime.api.types.ResourceMethodType; 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. @@ -79,4 +82,20 @@ private RemoteMethodType[] getRemoteMethods(MethodType[] methodTypes) { public ResourceMethodType[] getResourceMethods() { return resourceMethods; } + + @Override + protected Collection allMethods() { + Stream methodStream = Arrays.stream(getMethods()) + .filter(methodType -> !(SymbolFlags.isFlagOn(methodType.getFlags(), SymbolFlags.REMOTE) || + SymbolFlags.isFlagOn(methodType.getFlags(), SymbolFlags.RESOURCE))) + .map(MethodData::fromMethod); + Stream remoteMethodStream = + Arrays.stream(getRemoteMethods()) + .map(MethodData::fromRemoteMethod); + Stream resourceMethodStream = + Arrays.stream(getResourceMethods()) + .map(method -> MethodData.fromResourceMethod( + (BResourceMethodType) method)); + return Stream.concat(methodStream, Stream.concat(remoteMethodStream, resourceMethodStream)).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 f57eedcc6dcf..299b860ca917 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,29 +21,25 @@ import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.NeverType; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; /** * {@code BNeverType} represents the type of a {@code Never}. * * @since 2.0.0-preview1 */ -public class BNeverType extends BNullType implements NeverType { +public final class BNeverType extends BNullType implements NeverType { /** * Create a {@code BNeverType} represents the type of a {@code Never}. * * @param pkg package path */ public BNeverType(Module pkg) { - super(TypeConstants.NEVER_TNAME, pkg); + super(TypeConstants.NEVER_TNAME, pkg, Builder.getNeverType(), TypeTags.NEVER_TAG); } @Override public boolean isAnydata() { return true; } - - @Override - public int getTag() { - return TypeTags.NEVER_TAG; - } } 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 9070784980fc..02e846517c71 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,18 @@ import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.types.NullType; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.function.Supplier; /** * {@code BNullType} represents the type of a {@code NullLiteral}. * * @since 0.995.0 */ -public class BNullType extends BType implements NullType { +public sealed class BNullType extends BSemTypeWrapper implements NullType permits BNeverType { /** * Create a {@code BNullType} represents the type of a {@code NullLiteral}. @@ -35,31 +40,47 @@ 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), typeName, pkg, TypeTags.NULL_TAG, Builder.getNilType()); } - @Override - public V getZeroValue() { - return null; + protected BNullType(String typeName, Module pkg, SemType semType, int tag) { + this(() -> new BNullTypeImpl(typeName, pkg), typeName, pkg, tag, semType); } - @Override - public V getEmptyValue() { - return null; + private BNullType(Supplier bNullTypeSupplier, String typeName, Module pkg, int tag, + SemType semType) { + super(new ConcurrentLazySupplier<>(bNullTypeSupplier), typeName, pkg, tag, semType); } - @Override - public int getTag() { - return TypeTags.NULL_TAG; - } + protected static final class BNullTypeImpl extends BType implements NullType { - @Override - public boolean isNilable() { - return true; - } + private BNullTypeImpl(String typeName, Module pkg) { + super(typeName, pkg, null); + } + + @Override + public V getZeroValue() { + return null; + } + + @Override + public V getEmptyValue() { + return null; + } + + @Override + public int getTag() { + return TypeTags.NULL_TAG; + } + + @Override + public boolean isNilable() { + return true; + } - @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/BObjectType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BObjectType.java index 05cd15e570fc..713a96f0d469 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 @@ -22,25 +22,49 @@ 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.TypeTags; +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.types.semtype.ShapeAnalyzer; 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.TypeChecker; import io.ballerina.runtime.internal.scheduling.Scheduler; import io.ballerina.runtime.internal.scheduling.Strand; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.DefinitionContainer; +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.utils.ValueUtils; +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 java.util.function.Function; import static io.ballerina.runtime.api.types.TypeTags.SERVICE_TAG; @@ -49,7 +73,7 @@ * * @since 0.995.0 */ -public class BObjectType extends BStructureType implements ObjectType { +public class BObjectType extends BStructureType implements ObjectType, TypeWithShape { private MethodType[] methodTypes; private MethodType initMethod; @@ -62,6 +86,9 @@ public class BObjectType extends BStructureType implements ObjectType { private String cachedToString; private boolean resolving; + private final DefinitionContainer defn = new DefinitionContainer<>(); + private final DefinitionContainer acceptedTypeDefn = new DefinitionContainer<>(); + private volatile DistinctIdSupplier distinctIdSupplier; /** * Create a {@code BObjectType} which represents the user defined struct type. @@ -215,6 +242,10 @@ public void setIntersectionType(IntersectionType intersectionType) { public void setTypeIdSet(BTypeIdSet typeIdSet) { this.typeIdSet = typeIdSet; + synchronized (this) { + this.distinctIdSupplier = null; + } + resetSemType(); } public BObjectType duplicate() { @@ -243,6 +274,270 @@ public boolean hasAnnotations() { @Override public TypeIdSet getTypeIdSet() { + if (typeIdSet == null) { + return new BTypeIdSet(); + } return new BTypeIdSet(new ArrayList<>(typeIdSet.ids)); } + + @Override + public final SemType createSemType() { + Env env = Env.getInstance(); + initializeDistinctIdSupplierIfNeeded(env); + CellAtomicType.CellMutability mut = + SymbolFlags.isFlagOn(getFlags(), SymbolFlags.READONLY) ? CellAtomicType.CellMutability.CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + SemType innerType; + if (defn.isDefinitionReady()) { + innerType = defn.getSemType(env); + } else { + var result = defn.trySetDefinition(ObjectDefinition::new); + if (!result.updated()) { + innerType = defn.getSemType(env); + } else { + ObjectDefinition od = result.definition(); + innerType = semTypeInner(od, mut, SemType::tryInto); + } + } + return distinctIdSupplier.get().stream().map(ObjectDefinition::distinct).reduce(innerType, Core::intersect); + } + + private static boolean skipField(Set seen, String name) { + if (name.startsWith("$")) { + return true; + } + return !seen.add(name); + } + + private SemType semTypeInner(ObjectDefinition od, CellAtomicType.CellMutability mut, + Function semTypeSupplier) { + Env env = Env.getInstance(); + ObjectQualifiers qualifiers = getObjectQualifiers(); + List members = new ArrayList<>(); + Set seen = new HashSet<>(); + 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); + members.add(new Member(name, semTypeSupplier.apply(field.getFieldType()), Member.Kind.Field, + isPublic ? Member.Visibility.Public : Member.Visibility.Private, isImmutable)); + } + for (MethodData method : allMethods()) { + String name = method.name(); + if (skipField(seen, name)) { + continue; + } + boolean isPublic = SymbolFlags.isFlagOn(method.flags(), SymbolFlags.PUBLIC); + members.add(new Member(name, method.semType(), Member.Kind.Method, + isPublic ? Member.Visibility.Public : Member.Visibility.Private, true)); + } + return od.define(env, qualifiers, members, mut); + } + + 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 Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + if (!couldInherentTypeBeDifferent()) { + return Optional.of(getSemType()); + } + AbstractObjectValue abstractObjectValue = (AbstractObjectValue) object; + SemType cachedShape = abstractObjectValue.shapeOf(); + if (cachedShape != null) { + return Optional.of(cachedShape); + } + initializeDistinctIdSupplierIfNeeded(cx.env); + SemType shape = distinctIdSupplier.get().stream().map(ObjectDefinition::distinct) + .reduce(valueShape(cx, shapeSupplier, abstractObjectValue), Core::intersect); + abstractObjectValue.cacheShape(shape); + return Optional.of(shape); + } + + private void initializeDistinctIdSupplierIfNeeded(Env env) { + if (distinctIdSupplier == null) { + synchronized (this) { + if (distinctIdSupplier == null) { + distinctIdSupplier = new DistinctIdSupplier(env, typeIdSet); + } + } + } + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Object object) { + return Optional.of(valueShape(cx, shapeSupplierFn, (AbstractObjectValue) object)); + } + + @Override + public final Optional acceptedTypeOf(Context cx) { + Env env = Env.getInstance(); + initializeDistinctIdSupplierIfNeeded(cx.env); + CellAtomicType.CellMutability mut = CellAtomicType.CellMutability.CELL_MUT_UNLIMITED; + SemType innerType; + if (acceptedTypeDefn.isDefinitionReady()) { + innerType = acceptedTypeDefn.getSemType(env); + } else { + var result = acceptedTypeDefn.trySetDefinition(ObjectDefinition::new); + if (!result.updated()) { + innerType = acceptedTypeDefn.getSemType(env); + } else { + ObjectDefinition od = result.definition(); + innerType = semTypeInner(od, mut, (type -> ShapeAnalyzer.acceptedTypeOf(cx, type).orElseThrow())); + } + } + return Optional.of( + distinctIdSupplier.get().stream().map(ObjectDefinition::distinct).reduce(innerType, Core::intersect)); + } + + @Override + public boolean couldInherentTypeBeDifferent() { + if (SymbolFlags.isFlagOn(getFlags(), SymbolFlags.READONLY)) { + return true; + } + return fields.values().stream().anyMatch( + field -> SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY) || + SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.FINAL)); + } + + private SemType valueShape(Context cx, ShapeSupplier shapeSupplier, AbstractObjectValue object) { + ObjectDefinition readonlyShapeDefinition = object.getReadonlyShapeDefinition(); + if (readonlyShapeDefinition != null) { + return readonlyShapeDefinition.getSemType(cx.env); + } + ObjectDefinition od = new ObjectDefinition(); + object.setReadonlyShapeDefinition(od); + List members = new ArrayList<>(); + Set seen = new HashSet<>(fields.size() + methodTypes.length); + ObjectQualifiers qualifiers = getObjectQualifiers(); + 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); + members.add(new Member(name, fieldShape(cx, shapeSupplier, field, object, isImmutable), Member.Kind.Field, + isPublic ? Member.Visibility.Public : Member.Visibility.Private, isImmutable)); + } + for (MethodData method : allMethods()) { + String name = method.name(); + if (skipField(seen, name)) { + continue; + } + boolean isPublic = SymbolFlags.isFlagOn(method.flags(), SymbolFlags.PUBLIC); + members.add(new Member(name, method.semType(), Member.Kind.Method, + isPublic ? Member.Visibility.Public : Member.Visibility.Private, true)); + } + return od.define(cx.env, qualifiers, members, + qualifiers.readonly() ? CellAtomicType.CellMutability.CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + } + + private static SemType fieldShape(Context cx, ShapeSupplier shapeSupplier, Field field, + AbstractObjectValue objectValue, boolean isImmutable) { + if (!isImmutable) { + return SemType.tryInto(field.getFieldType()); + } + BString fieldName = StringUtils.fromString(field.getFieldName()); + Optional shape = shapeSupplier.get(cx, objectValue.get(fieldName)); + assert !shape.isEmpty(); + return shape.get(); + } + + @Override + public void resetSemType() { + defn.clear(); + super.resetSemType(); + } + + protected Collection allMethods() { + if (methodTypes == null) { + return List.of(); + } + return Arrays.stream(methodTypes) + .map(MethodData::fromMethod).toList(); + } + + protected record MethodData(String name, long flags, SemType semType) { + + static MethodData fromMethod(MethodType method) { + return new MethodData(method.getName(), method.getFlags(), + tryInto(method.getType())); + } + + static MethodData fromRemoteMethod(MethodType method) { + // Remote methods need to be distinct with remote methods only there can be instance methods with the same + // name + return new MethodData("@remote_" + method.getName(), method.getFlags(), + tryInto(method.getType())); + } + + static MethodData fromResourceMethod(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<>(); + for (Type part : pathSegmentTypes) { + if (part == null) { + paramTypes.add(Builder.getAnyType()); + } else { + paramTypes.add(tryInto(part)); + } + } + for (Parameter paramType : innerFn.getParameters()) { + paramTypes.add(tryInto(paramType.type)); + } + SemType rest; + Type restType = innerFn.getRestType(); + if (restType instanceof BArrayType arrayType) { + rest = tryInto(arrayType.getElementType()); + } else { + rest = Builder.getNeverType(); + } + + SemType returnType; + if (innerFn.getReturnType() != null) { + returnType = tryInto(innerFn.getReturnType()); + } else { + returnType = Builder.getNilType(); + } + ListDefinition paramListDefinition = new ListDefinition(); + Env env = TypeChecker.context().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, ((BFunctionType) innerFn).getQualifiers()); + return new MethodData(methodName, method.getFlags(), semType); + } + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return fields.values().stream().map(Field::getFieldType).filter(each -> each instanceof MayBeDependentType) + .anyMatch(each -> ((MayBeDependentType) each).isDependentlyTyped(visited)); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BParameterizedType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BParameterizedType.java index 9ab78cf7271c..05b9229d68ac 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BParameterizedType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BParameterizedType.java @@ -21,6 +21,9 @@ import io.ballerina.runtime.api.types.ParameterizedType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.Set; /** * {@code ParameterizedType} represents the parameterized type in dependently-typed functions. @@ -80,4 +83,14 @@ public Type getParamValueType() { public int getParamIndex() { return this.paramIndex; } + + @Override + public SemType createSemType() { + return SemType.tryInto(this.paramValueType); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return true; + } } 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 c945f4941839..c4366334b3fa 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.types.ReadonlyType; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; import io.ballerina.runtime.internal.values.RefValue; /** @@ -27,34 +29,41 @@ * * @since 1.3.0 */ -public class BReadonlyType extends BType implements ReadonlyType { +public final class BReadonlyType extends BSemTypeWrapper implements ReadonlyType { public BReadonlyType(String typeName, Module pkg) { - super(typeName, pkg, RefValue.class); + super(new ConcurrentLazySupplier<>(() -> new BReadonlyTypeImpl(typeName, pkg)), typeName, pkg, + TypeTags.READONLY_TAG, Builder.getReadonlyType()); } - @Override - public V getZeroValue() { - return null; - } + protected static final class BReadonlyTypeImpl extends BType implements ReadonlyType { - @Override - public V getEmptyValue() { - return null; - } + private BReadonlyTypeImpl(String typeName, Module pkg) { + super(typeName, pkg, RefValue.class); + } - @Override - public int getTag() { - return TypeTags.READONLY_TAG; - } + @Override + public V getZeroValue() { + return null; + } - @Override - public boolean isNilable() { - return true; - } + @Override + public V getEmptyValue() { + return null; + } + + @Override + public int getTag() { + return TypeTags.READONLY_TAG; + } + + public boolean isNilable() { + return true; + } - @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/BRecordType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BRecordType.java index 2af42d5596d2..f64e13db2304 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 @@ -20,6 +20,7 @@ import io.ballerina.identifier.Utils; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.flags.SymbolFlags; import io.ballerina.runtime.api.flags.TypeFlags; @@ -28,26 +29,44 @@ import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +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.types.semtype.ShapeAnalyzer; 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.scheduling.Scheduler; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability; +import io.ballerina.runtime.internal.types.semtype.DefinitionContainer; +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 java.util.function.Function; + +import static io.ballerina.runtime.api.types.semtype.Builder.getNeverType; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_UNLIMITED; /** * {@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, TypeWithShape { private final String internalName; public boolean sealed; public Type restFieldType; @@ -55,6 +74,9 @@ public class BRecordType extends BStructureType implements RecordType { private final boolean readonly; private IntersectionType immutableType; private IntersectionType intersectionType = null; + private final DefinitionContainer defn = new DefinitionContainer<>(); + private final DefinitionContainer acceptedTypeDefn = new DefinitionContainer<>(); + private byte couldInhereTypeBeDifferentCache = 0; private final Map defaultValues = new LinkedHashMap<>(); @@ -73,6 +95,7 @@ public BRecordType(String typeName, String internalName, Module pkg, long flags, this.sealed = sealed; this.typeFlags = typeFlags; this.readonly = SymbolFlags.isFlagOn(flags, SymbolFlags.READONLY); + TypeCreator.registerRecordType(this); } /** @@ -102,6 +125,7 @@ public BRecordType(String typeName, Module pkg, long flags, Map f this.fields = fields; } this.internalName = typeName; + TypeCreator.registerRecordType(this); } private Map getReadOnlyFields(Map fields) { @@ -218,4 +242,190 @@ public Map getDefaultValues() { return defaultValues; } + @Override + public SemType createSemType() { + Env env = Env.getInstance(); + if (defn.isDefinitionReady()) { + return defn.getSemType(env); + } + var result = defn.trySetDefinition(MappingDefinition::new); + if (!result.updated()) { + return defn.getSemType(env); + } + MappingDefinition md = result.definition(); + return createSemTypeInner(md, env, mut(), SemType::tryInto); + } + + private CellMutability mut() { + return isReadOnly() ? CELL_MUT_NONE : CellMutability.CELL_MUT_LIMITED; + } + + private SemType createSemTypeInner(MappingDefinition md, Env env, CellMutability mut, + Function semTypeFunction) { + Field[] fields = getFields().values().toArray(Field[]::new); + MappingDefinition.Field[] mappingFields = new MappingDefinition.Field[fields.length]; + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + boolean isOptional = SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL); + SemType fieldType = semTypeFunction.apply(field.getFieldType()); + if (!isOptional && Core.isNever(fieldType)) { + return getNeverType(); + } + boolean isReadonly = + SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY) || Core.isNever(fieldType); + mappingFields[i] = new MappingDefinition.Field(field.getFieldName(), fieldType, + isReadonly, isOptional); + } + SemType rest; + rest = restFieldType != null ? semTypeFunction.apply(restFieldType) : getNeverType(); + return md.defineMappingTypeWrapped(env, mappingFields, rest, mut); + } + + @Override + public void resetSemType() { + defn.clear(); + super.resetSemType(); + } + + @Override + public boolean isDependentlyTypedInner(Set visited) { + return fields.values().stream().map(Field::getFieldType).filter(each -> each instanceof MayBeDependentType) + .anyMatch(each -> ((MayBeDependentType) each).isDependentlyTyped(visited)); + } + + @Override + public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + if (!couldInherentTypeBeDifferent()) { + return Optional.of(getSemType()); + } + MapValueImpl value = (MapValueImpl) object; + SemType cachedSemType = value.shapeOf(); + if (cachedSemType != null) { + return Optional.of(cachedSemType); + } + SemType semTypePart = shapeOfInner(cx, shapeSupplier, value, isReadOnly()); + value.cacheShape(semTypePart); + return Optional.of(semTypePart); + } + + private SemType shapeOfInner(Context cx, ShapeSupplier shapeSupplier, MapValueImpl value, + boolean takeFieldShape) { + Env env = cx.env; + int nFields = value.size(); + Map.Entry[] entries = value.entrySet().toArray(Map.Entry[]::new); + Set handledFields = new HashSet<>(nFields); + MappingDefinition md; + if (takeFieldShape) { + MappingDefinition readonlyShapeDefinition = value.getReadonlyShapeDefinition(); + if (readonlyShapeDefinition != null) { + return readonlyShapeDefinition.getSemType(env); + } else { + md = new MappingDefinition(); + value.setReadonlyShapeDefinition(md); + } + } else { + md = new MappingDefinition(); + } + List fields = new ArrayList<>(nFields); + for (int i = 0; i < nFields; i++) { + String fieldName = entries[i].getKey().toString(); + Object fieldValue = entries[i].getValue(); + handledFields.add(fieldName); + fields.add(fieldShape(cx, shapeSupplier, fieldName, fieldValue, takeFieldShape)); + } + if (!takeFieldShape) { + getFields().values().stream() + .filter(field -> !handledFields.contains(field.getFieldName())) + .map(field -> fieldShapeWithoutValue(field, field.getFieldName())) + .forEach(fields::add); + } + MappingDefinition.Field[] fieldsArray = fields.toArray(MappingDefinition.Field[]::new); + SemType rest; + if (takeFieldShape) { + rest = Builder.getNeverType(); + } else { + rest = restFieldType != null ? SemType.tryInto(restFieldType) : getNeverType(); + } + SemType shape = md.defineMappingTypeWrapped(env, fieldsArray, rest, mut()); + value.resetReadonlyShapeDefinition(); + return shape; + } + + private MappingDefinition.Field fieldShapeWithoutValue(Field field, String fieldName) { + boolean isOptional = fieldIsOptional(fieldName); + boolean isReadonly = fieldIsReadonly(fieldName); + SemType fieldType = SemType.tryInto(field.getFieldType()); + if (isReadonly && isOptional) { + fieldType = Builder.getUndefType(); + } + MappingDefinition.Field field1 = new MappingDefinition.Field(field.getFieldName(), fieldType, + isReadonly, isOptional); + return field1; + } + + @Override + public boolean couldInherentTypeBeDifferent() { + if (couldInhereTypeBeDifferentCache != 0) { + return couldInhereTypeBeDifferentCache == 1; + } + boolean result = couldShapeBeDifferentInner(); + couldInhereTypeBeDifferentCache = (byte) (result ? 1 : 2); + return result; + } + + private boolean couldShapeBeDifferentInner() { + if (isReadOnly()) { + return true; + } + return fields.values().stream().anyMatch(field -> SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY)); + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + return Optional.of(shapeOfInner(cx, shapeSupplier, (MapValueImpl) object, true)); + } + + @Override + public Optional acceptedTypeOf(Context cx) { + Env env = cx.env; + if (acceptedTypeDefn.isDefinitionReady()) { + return Optional.of(acceptedTypeDefn.getSemType(env)); + } + var result = acceptedTypeDefn.trySetDefinition(MappingDefinition::new); + if (!result.updated()) { + return Optional.of(acceptedTypeDefn.getSemType(env)); + } + MappingDefinition md = result.definition(); + return Optional.of(createSemTypeInner(md, env, CELL_MUT_UNLIMITED, + (type) -> ShapeAnalyzer.acceptedTypeOf(cx, type).orElseThrow())); + } + + 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); + } + + private MappingDefinition.Field fieldShape(Context cx, ShapeSupplier shapeSupplier, String fieldName, + Object fieldValue, boolean alwaysTakeValueShape) { + boolean readonlyField = fieldIsReadonly(fieldName); + boolean optionalField = fieldIsOptional(fieldName); + SemType fieldType; + if (alwaysTakeValueShape || readonlyField) { + optionalField = false; + fieldType = shapeSupplier.get(cx, fieldValue).orElseThrow(); + } else { + fieldType = SemType.tryInto(fieldType(fieldName)); + } + return new MappingDefinition.Field(fieldName, fieldType, readonlyField, optionalField); + } } 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..4c1f5ed85391 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeWrapper.java @@ -0,0 +1,206 @@ +/* + * 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.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.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.ImmutableSemType; + +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; + +/** + * 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. + * + * @param The type of the {@code BType} that is being wrapped. + * @since 2201.11.0 + */ +public sealed class BSemTypeWrapper extends ImmutableSemType implements Type, MayBeDependentType + permits BAnyType, BBooleanType, BByteType, BDecimalType, BFloatType, BHandleType, BIntegerType, BNullType, + BReadonlyType, BStringType { + + private Type cachedReferredType = null; + private Type cachedImpliedType = null; + + private final Supplier bTypeSupplier; + private final int tag; + protected final String typeName; // Debugger uses this field to show the type name + private final Module pkg; + + protected BSemTypeWrapper(Supplier bTypeSupplier, String typeName, Module pkg, int tag, SemType semType) { + super(semType); + this.bTypeSupplier = bTypeSupplier; + this.typeName = typeName; + this.tag = tag; + this.pkg = pkg; + } + + public final Class getValueClass() { + return getbType().getValueClass(); + } + + @Override + public final V getZeroValue() { + return getbType().getZeroValue(); + } + + @Override + public final V getEmptyValue() { + return getbType().getEmptyValue(); + } + + @Override + public final int getTag() { + return tag; + } + + @Override + public final String toString() { + return getbType().toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BSemTypeWrapper other)) { + return false; + } + return Objects.equals(this.typeName, other.typeName) && Objects.equals(this.pkg, other.pkg); + } + + @Override + public final boolean isNilable() { + return Core.containsBasicType(this, Builder.getNilType()); + } + + @Override + public final int hashCode() { + return Objects.hash(this.typeName, this.pkg); + } + + @Override + public String getName() { + return typeName == null ? "" : typeName; + } + + @Override + public String getQualifiedName() { + String name = getName(); + if (name.isEmpty()) { + return ""; + } + + return pkg == null ? name : pkg + ":" + name; + } + + @Override + public Module getPackage() { + return pkg; + } + + @Override + public boolean isPublic() { + return getbType().isPublic(); + } + + @Override + public boolean isNative() { + return getbType().isNative(); + } + + @Override + public boolean isAnydata() { + Context cx = TypeChecker.context(); + return Core.isSubType(cx, this, Builder.getAnyDataType()); + } + + @Override + public boolean isPureType() { + Context cx = TypeChecker.context(); + return Core.isSubType(cx, this, Builder.getErrorType()) || isAnydata(); + } + + @Override + public boolean isReadOnly() { + Context cx = TypeChecker.context(); + return Core.isSubType(cx, this, Builder.getReadonlyType()); + } + + @Override + public Type getImmutableType() { + return getbType().getImmutableType(); + } + + @Override + public void setImmutableType(IntersectionType immutableType) { + getbType().setImmutableType(immutableType); + } + + @Override + public Module getPkg() { + return pkg; + } + + @Override + public long getFlags() { + return getbType().getFlags(); + } + + @Override + public void setCachedReferredType(Type type) { + cachedReferredType = type; + } + + @Override + public Type getCachedReferredType() { + return cachedReferredType; + } + + @Override + public void setCachedImpliedType(Type type) { + cachedImpliedType = type; + } + + @Override + public Type getCachedImpliedType() { + return cachedImpliedType; + } + + protected E getbType() { + return bTypeSupplier.get(); + } + + @Override + public boolean isDependentlyTyped() { + return false; + } + + @Override + public boolean isDependentlyTyped(Set visited) { + return isDependentlyTyped(); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStreamType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStreamType.java index 494dc1d54642..1382a3e29943 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStreamType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStreamType.java @@ -24,9 +24,16 @@ import io.ballerina.runtime.api.types.StreamType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.DefinitionContainer; +import io.ballerina.runtime.internal.types.semtype.StreamDefinition; import io.ballerina.runtime.internal.values.StreamValue; import java.util.Objects; +import java.util.Set; /** * {@link BStreamType} represents streaming data in Ballerina. @@ -37,6 +44,7 @@ public class BStreamType extends BType implements StreamType { private final Type constraint; private final Type completionType; + private final DefinitionContainer definition = new DefinitionContainer<>(); /** * Creates a {@link BStreamType} which represents the stream type. @@ -135,4 +143,29 @@ public boolean equals(Object obj) { return Objects.equals(constraint, other.constraint) && Objects.equals(completionType, other.completionType); } + + @Override + public SemType createSemType() { + if (constraint == null) { + return Builder.getStreamType(); + } + Env env = TypeChecker.context().env; + if (definition.isDefinitionReady()) { + return definition.getSemType(env); + } + var result = definition.trySetDefinition(StreamDefinition::new); + if (!result.updated()) { + return definition.getSemType(env); + } + StreamDefinition sd = result.definition(); + return sd.define(env, tryInto(constraint), tryInto(completionType)); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return (constraint instanceof MayBeDependentType constrainedType && + constrainedType.isDependentlyTyped(visited)) || + (completionType instanceof MayBeDependentType completionType && + completionType.isDependentlyTyped(visited)); + } } 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 0211eef19b38..7ddbd3bf0f7c 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 @@ -19,8 +19,14 @@ import io.ballerina.runtime.api.Module; 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.TypeTags; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.ConcurrentLazySupplier; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.function.Supplier; /** * {@code BStringType} represents a String type in ballerina. @@ -28,9 +34,13 @@ * @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 Module DEFAULT_MODULE = new Module(null, null, null); + private static final BStringTypeImpl DEFAULT_B_TYPE = + new BStringTypeImpl(TypeConstants.STRING_TNAME, DEFAULT_MODULE, TypeTags.STRING_TAG); /** * Create a {@code BStringType} which represents the boolean type. @@ -38,32 +48,64 @@ 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), typeName, pkg, TypeTags.STRING_TAG, + Builder.getStringType()); } public BStringType(String typeName, Module pkg, int tag) { - super(typeName, pkg, String.class); - this.tag = tag; + this(() -> new BStringTypeImpl(typeName, pkg, tag), typeName, pkg, tag, pickSemtype(tag)); } - @Override - public V getZeroValue() { - return (V) RuntimeConstants.STRING_EMPTY_VALUE; + private BStringType(Supplier bTypeSupplier, String typeName, Module pkg, int tag, + SemType semType) { + super(new ConcurrentLazySupplier<>(bTypeSupplier), typeName, pkg, tag, semType); } - @Override - public V getEmptyValue() { - return (V) RuntimeConstants.STRING_EMPTY_VALUE; + public static BStringType singletonType(String value) { + return new BStringType(() -> (BStringTypeImpl) DEFAULT_B_TYPE.clone(), TypeConstants.STRING_TNAME, + DEFAULT_MODULE, TypeTags.STRING_TAG, Builder.getStringConst(value)); } - @Override - public int getTag() { - return tag; + private static SemType pickSemtype(int tag) { + return switch (tag) { + case TypeTags.STRING_TAG -> Builder.getStringType(); + case TypeTags.CHAR_STRING_TAG -> Builder.getCharType(); + default -> throw new IllegalStateException("Unexpected string type tag: " + tag); + }; } - @Override - public boolean isReadOnly() { - return true; + protected 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; + } + + @Override + 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 + public BType clone() { + return super.clone(); + } } } 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 64cf037ebde1..2acdcf181db7 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 @@ -72,6 +72,7 @@ public Map getFields() { @Override public void setFields(Map fields) { this.fields = fields; + resetSemType(); } @Override diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTableType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTableType.java index 14c7d2b803e2..df743c54c344 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTableType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTableType.java @@ -22,18 +22,27 @@ import io.ballerina.runtime.api.types.TableType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +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.types.semtype.ShapeAnalyzer; +import io.ballerina.runtime.api.values.BTable; +import io.ballerina.runtime.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.TableUtils; import io.ballerina.runtime.internal.values.ReadOnlyUtils; import io.ballerina.runtime.internal.values.TableValue; import io.ballerina.runtime.internal.values.TableValueImpl; import java.util.Optional; +import java.util.Set; /** * {@code BTableType} represents tabular data in Ballerina. * * @since 1.3.0 */ -public class BTableType extends BType implements TableType { +public class BTableType extends BType implements TableType, TypeWithShape { private final Type constraint; private Type keyType; @@ -162,4 +171,85 @@ public void setIntersectionType(IntersectionType intersectionType) { public boolean isAnydata() { return this.constraint.isAnydata(); } + + @Override + public SemType createSemType() { + return createSemTypeWithConstraint(tryInto(constraint)); + } + + private SemType createSemTypeWithConstraint(SemType constraintType) { + SemType semType; + Context cx = TypeChecker.context(); + if (fieldNames.length > 0) { + semType = TableUtils.tableContainingKeySpecifier(cx, constraintType, fieldNames); + } else if (keyType != null) { + semType = TableUtils.tableContainingKeyConstraint(cx, constraintType, tryInto(keyType)); + } else { + semType = TableUtils.tableContaining(cx.env, constraintType); + } + + if (isReadOnly()) { + semType = Core.intersect(semType, Builder.getReadonlyType()); + } + return semType; + } + + @Override + public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + if (!couldInherentTypeBeDifferent()) { + return Optional.of(getSemType()); + } + BTable table = (BTable) object; + SemType cachedShape = table.shapeOf(); + if (cachedShape != null) { + return Optional.of(cachedShape); + } + SemType semtype = valueShape(cx, shapeSupplier, table); + return Optional.of(semtype); + } + + @Override + public boolean couldInherentTypeBeDifferent() { + return isReadOnly(); + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Object object) { + return Optional.of(valueShape(cx, shapeSupplierFn, (BTable) object)); + } + + @Override + public Optional acceptedTypeOf(Context cx) { + SemType constraintType = ShapeAnalyzer.acceptedTypeOf(cx, this.constraint).orElseThrow(); + SemType semType; + if (fieldNames.length > 0) { + semType = TableUtils.acceptedTypeContainingKeySpecifier(cx, constraintType, fieldNames); + } else if (keyType != null) { + SemType keyAcceptedType = ShapeAnalyzer.acceptedTypeOf(cx, keyType).orElseThrow(); + semType = TableUtils.acceptedTypeContainingKeyConstraint(cx, constraintType, keyAcceptedType); + } else { + semType = TableUtils.acceptedType(cx.env, constraintType); + } + return Optional.of(semType); + } + + private SemType valueShape(Context cx, ShapeSupplier shapeSupplier, BTable table) { + SemType constraintType = Builder.getNeverType(); + for (var value : table.values()) { + SemType valueShape = shapeSupplier.get(cx, value).orElse(SemType.tryInto(constraint)); + constraintType = Core.union(constraintType, valueShape); + } + return createSemTypeWithConstraint(constraintType); + } + + @Override + public boolean shouldCache() { + // TODO: remove this once we have fixed equals + return false; + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return constraint instanceof MayBeDependentType constraintType && constraintType.isDependentlyTyped(visited); + } } 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 e4758fd5e8b8..ccda11d6f298 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,15 @@ import io.ballerina.runtime.api.types.TupleType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +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.types.semtype.ShapeAnalyzer; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.DefinitionContainer; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; +import io.ballerina.runtime.internal.values.AbstractArrayValue; import io.ballerina.runtime.internal.values.ReadOnlyUtils; import io.ballerina.runtime.internal.values.TupleValueImpl; @@ -31,25 +40,36 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; +import static io.ballerina.runtime.api.types.semtype.Builder.getNeverType; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_UNLIMITED; + /** * {@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, TypeWithShape { private List tupleTypes; private Type restType; private int typeFlags; private final boolean readonly; + // This is used avoid unnecessary flag updates when we change the members. If this + // is set before accessing flags you must call {@code checkAllMembers}. + private volatile boolean flagsPoisoned = false; private IntersectionType immutableType; private IntersectionType intersectionType = null; public boolean isCyclic = false; private boolean resolving; private boolean resolvingReadonly; private String cachedToString; + private final DefinitionContainer defn = new DefinitionContainer<>(); + private final DefinitionContainer acceptedTypeDefn = new DefinitionContainer<>(); /** * Create a {@code BTupleType} which represents the tuple type. @@ -60,7 +80,7 @@ public BTupleType(List typeList) { super(null, null, Object.class); this.tupleTypes = typeList; this.restType = null; - checkAllMembers(); + this.flagsPoisoned = true; this.readonly = false; } @@ -154,6 +174,7 @@ public void setCyclic(boolean isCyclic) { } public void setMemberTypes(List members, Type restType) { + resetSemType(); if (members == null) { return; } @@ -166,7 +187,8 @@ public void setMemberTypes(List members, Type restType) { this.tupleTypes = members; this.restType = restType; } - checkAllMembers(); + flagsPoisoned = true; + defn.clear(); } @Override @@ -254,16 +276,24 @@ public boolean equals(Object o) { @Override public boolean isAnydata() { - return TypeFlags.isFlagOn(this.typeFlags, TypeFlags.ANYDATA); + return TypeFlags.isFlagOn(getTypeFlags(), TypeFlags.ANYDATA); } @Override public boolean isPureType() { - return TypeFlags.isFlagOn(this.typeFlags, TypeFlags.PURETYPE); + return TypeFlags.isFlagOn(getTypeFlags(), TypeFlags.PURETYPE); } @Override public int getTypeFlags() { + if (flagsPoisoned) { + synchronized (this) { + if (flagsPoisoned) { + checkAllMembers(); + flagsPoisoned = false; + } + } + } return this.typeFlags; } @@ -299,4 +329,108 @@ public void setIntersectionType(IntersectionType intersectionType) { public String getAnnotationKey() { return Utils.decodeIdentifier(this.typeName); } + + @Override + public SemType createSemType() { + Env env = Env.getInstance(); + if (defn.isDefinitionReady()) { + return defn.getSemType(env); + } + var result = defn.trySetDefinition(ListDefinition::new); + if (!result.updated()) { + return defn.getSemType(env); + } + ListDefinition ld = result.definition(); + return createSemTypeInner(env, ld, SemType::tryInto, mut()); + } + + private CellAtomicType.CellMutability mut() { + return isReadOnly() ? CELL_MUT_NONE : CellAtomicType.CellMutability.CELL_MUT_LIMITED; + } + + private SemType createSemTypeInner(Env env, ListDefinition ld, Function semTypeFunction, + CellAtomicType.CellMutability mut) { + SemType[] memberTypes = new SemType[tupleTypes.size()]; + for (int i = 0; i < tupleTypes.size(); i++) { + SemType memberType = semTypeFunction.apply(tupleTypes.get(i)); + if (Core.isNever(memberType)) { + return getNeverType(); + } + memberTypes[i] = memberType; + } + SemType rest = restType != null ? semTypeFunction.apply(restType) : getNeverType(); + return ld.defineListTypeWrapped(env, memberTypes, memberTypes.length, rest, mut); + } + + @Override + public void resetSemType() { + defn.clear(); + super.resetSemType(); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return tupleTypes.stream().filter(each -> each instanceof MayBeDependentType) + .anyMatch(each -> ((MayBeDependentType) each).isDependentlyTyped(visited)); + } + + @Override + public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + if (!couldInherentTypeBeDifferent()) { + return Optional.of(getSemType()); + } + AbstractArrayValue value = (AbstractArrayValue) object; + SemType cachedShape = value.shapeOf(); + if (cachedShape != null) { + return Optional.of(cachedShape); + } + SemType semType = shapeOfInner(cx, shapeSupplier, value); + value.cacheShape(semType); + return Optional.of(semType); + } + + @Override + public boolean couldInherentTypeBeDifferent() { + return isReadOnly(); + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + return Optional.of(shapeOfInner(cx, shapeSupplier, (AbstractArrayValue) object)); + } + + @Override + public Optional acceptedTypeOf(Context cx) { + Env env = cx.env; + if (acceptedTypeDefn.isDefinitionReady()) { + return Optional.of(acceptedTypeDefn.getSemType(env)); + } + var result = acceptedTypeDefn.trySetDefinition(ListDefinition::new); + if (!result.updated()) { + return Optional.of(acceptedTypeDefn.getSemType(env)); + } + ListDefinition ld = result.definition(); + return Optional.of(createSemTypeInner(env, ld, (type) -> ShapeAnalyzer.acceptedTypeOf(cx, type).orElseThrow(), + CELL_MUT_UNLIMITED)); + } + + private SemType shapeOfInner(Context cx, ShapeSupplier shapeSupplier, AbstractArrayValue value) { + Env env = cx.env; + ListDefinition defn = value.getReadonlyShapeDefinition(); + if (defn != null) { + return defn.getSemType(env); + } + int size = value.size(); + SemType[] memberTypes = new SemType[size]; + ListDefinition ld = new ListDefinition(); + value.setReadonlyShapeDefinition(ld); + for (int i = 0; i < size; i++) { + Optional memberType = shapeSupplier.get(cx, value.get(i)); + assert memberType.isPresent(); + memberTypes[i] = memberType.get(); + } + SemType semType = ld.defineListTypeWrapped(env, memberTypes, memberTypes.length, getNeverType(), mut()); + value.resetReadonlyShapeDefinition(); + return 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 d6cd9c996ba3..62c285dbe04d 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java @@ -22,10 +22,20 @@ import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.CacheableTypeDescriptor; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.TypeCheckCache; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.MutableSemType; +import java.util.HashSet; import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * {@code BType} represents a type in Ballerina. @@ -37,13 +47,18 @@ * * @since 0.995.0 */ -public abstract class BType implements Type { +public abstract non-sealed class BType extends SemType + implements Type, MutableSemType, Cloneable, CacheableTypeDescriptor, MayBeDependentType { + 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; + private volatile TypeCheckCache typeCheckCache; + private final ReadWriteLock typeCacheLock = new ReentrantReadWriteLock(); protected BType(String typeName, Module pkg, Class valueClass) { this.typeName = typeName; @@ -231,4 +246,97 @@ public void setCachedImpliedType(Type type) { public Type getCachedImpliedType() { return this.cachedImpliedType; } + + @Override + public SemType createSemType() { + throw new IllegalStateException("Child that are used for type checking must implement this method"); + } + + @Override + public void updateInnerSemTypeIfNeeded() { + if (cachedSemType == null) { + cachedSemType = createSemType(); + setAll(cachedSemType.all()); + setSome(cachedSemType.some(), cachedSemType.subTypeData()); + } + } + + protected SemType getSemType() { + updateInnerSemTypeIfNeeded(); + return cachedSemType; + } + + @Override + public void resetSemType() { + cachedSemType = null; + } + + @Override + public BType clone() { + try { + BType clone = (BType) super.clone(); + clone.cachedSemType = null; + clone.setCachedImpliedType(null); + clone.setCachedReferredType(null); + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + @Override + public boolean shouldCache() { + return this.pkg != null && this.typeName != null && !this.typeName.contains("$anon"); + } + + @Override + public final Optional cachedTypeCheckResult(Context cx, CacheableTypeDescriptor other) { + initializeCacheIfNeeded(cx); + typeCacheLock.readLock().lock(); + try { + return typeCheckCache.cachedTypeCheckResult(other); + } finally { + typeCacheLock.readLock().unlock(); + } + } + + private void initializeCacheIfNeeded(Context cx) { + typeCacheLock.readLock().lock(); + boolean shouldInitialize = typeCheckCache == null; + typeCacheLock.readLock().unlock(); + if (!shouldInitialize) { + return; + } + try { + typeCacheLock.writeLock().lock(); + if (typeCheckCache == null) { + typeCheckCache = cx.getTypeCheckCache(this); + } + } finally { + typeCacheLock.writeLock().unlock(); + } + } + + @Override + public final void cacheTypeCheckResult(CacheableTypeDescriptor other, boolean result) { + // This happening after checking the cache so it must be initialized by now + typeCheckCache.cacheTypeCheckResult(other, result); + } + + @Override + public final boolean isDependentlyTyped() { + return isDependentlyTyped(new HashSet<>()); + } + + @Override + public final boolean isDependentlyTyped(Set visited) { + if (!visited.add(this)) { + return false; + } + return isDependentlyTypedInner(visited); + } + + protected boolean isDependentlyTypedInner(Set visited) { + return false; + } } 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 cc2e78d6a319..2e6384699111 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,16 +25,20 @@ import io.ballerina.runtime.api.types.IntersectionType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; import java.util.Objects; import java.util.Optional; +import java.util.Set; /** * {@code TypeReferencedType} represents a type description which refers to another type. * * @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 +130,50 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + public SemType createSemType() { + Type referredType = getReferredType(); + return tryInto(referredType); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return getReferredType() instanceof MayBeDependentType refType && refType.isDependentlyTyped(visited); + } + + @Override + public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + if (!couldInherentTypeBeDifferent()) { + return Optional.of(getSemType()); + } + Type referredType = getReferredType(); + if (referredType instanceof TypeWithShape typeWithShape) { + return typeWithShape.inherentTypeOf(cx, shapeSupplier, object); + } + return Optional.empty(); + } + + @Override + public boolean couldInherentTypeBeDifferent() { + return referredType instanceof TypeWithShape typeWithShape && typeWithShape.couldInherentTypeBeDifferent(); + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Object object) { + Type referredType = getReferredType(); + if (referredType instanceof TypeWithShape typeWithShape) { + return typeWithShape.shapeOf(cx, shapeSupplierFn, object); + } + return ShapeAnalyzer.shapeOf(cx, referredType); + } + + @Override + public Optional acceptedTypeOf(Context cx) { + Type referredType = getReferredType(); + if (referredType instanceof TypeWithShape typeWithShape) { + return typeWithShape.acceptedTypeOf(cx); + } + return ShapeAnalyzer.acceptedTypeOf(cx, referredType); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypedescType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypedescType.java index 411d75cf9662..da696b1bf335 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypedescType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypedescType.java @@ -24,19 +24,28 @@ import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.TypedescType; +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.internal.TypeChecker; +import io.ballerina.runtime.internal.types.semtype.TypedescUtils; import io.ballerina.runtime.internal.values.TypedescValue; import io.ballerina.runtime.internal.values.TypedescValueImpl; +import java.util.Set; + /** * {@code BTypedescType} represents a type of a type in the Ballerina type system. * * @since 0.995.0 */ public class BTypedescType extends BType implements TypedescType { - private Type constraint; + + private final Type constraint; public BTypedescType(String typeName, Module pkg) { super(typeName, pkg, Object.class); + constraint = null; } public BTypedescType(Type constraint) { @@ -84,4 +93,20 @@ public boolean isReadOnly() { public String toString() { return "typedesc" + "<" + constraint.toString() + ">"; } + + @Override + public SemType createSemType() { + if (constraint == null) { + return Builder.getTypeDescType(); + } + SemType constraint = tryInto(getConstraint()); + Context cx = TypeChecker.context(); + return TypedescUtils.typedescContaining(cx.env, constraint); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return constraint instanceof MayBeDependentType constraintType && + constraintType.isDependentlyTyped(visited); + } } 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 4fe1f164c653..bdd97fe67536 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 @@ -21,10 +21,16 @@ import io.ballerina.runtime.api.flags.SymbolFlags; import io.ballerina.runtime.api.flags.TypeFlags; import io.ballerina.runtime.api.types.IntersectionType; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.SelectivelyImmutableReferenceType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.UnionType; +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.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.values.ReadOnlyUtils; @@ -44,7 +50,7 @@ * * @since 0.995.0 */ -public class BUnionType extends BType implements UnionType, SelectivelyImmutableReferenceType { +public class BUnionType extends BType implements UnionType, SelectivelyImmutableReferenceType, TypeWithAcceptedType { public boolean isCyclic = false; public static final String PIPE = "|"; @@ -167,6 +173,7 @@ public void setMemberTypes(Type[] members) { } this.memberTypes = readonly ? getReadOnlyTypes(members) : Arrays.asList(members); setFlagsBasedOnMembers(); + resetSemType(); } public void setOriginalMemberTypes(Type[] originalMemberTypes) { @@ -182,6 +189,9 @@ private void setMemberTypes(List members) { } private void setMemberTypes(List members, List originalMembers) { + if (memberTypes != null) { + resetSemType(); + } if (members == null) { return; } @@ -193,7 +203,6 @@ private void setMemberTypes(List members, List originalMembers) { this.memberTypes = readonly ? getReadOnlyTypes(members, new HashSet<>(members.size())) : members; this.resolvingReadonly = false; setFlagsBasedOnMembers(); - setOriginalMemberTypes(originalMembers); } @@ -231,12 +240,14 @@ private boolean checkNillable(List memberTypes) { } private void addMember(Type type) { + resetSemType(); this.memberTypes.add(type); setFlagsBasedOnMembers(); this.originalMemberTypes.add(type); } public void addMembers(Type... types) { + resetSemType(); this.memberTypes.addAll(Arrays.asList(types)); setFlagsBasedOnMembers(); this.originalMemberTypes.addAll(Arrays.asList(types)); @@ -446,7 +457,7 @@ public void mergeUnionType(BUnionType unionType) { this.addMember(newArrayType); continue; } - } else if (member instanceof BMapType mapType) { + } else if (member instanceof MapType mapType) { if (mapType.getConstrainedType() == unionType) { BMapType newMapType = new BMapType(this, this.readonly); this.addMember(newMapType); @@ -457,7 +468,7 @@ public void mergeUnionType(BUnionType unionType) { BTableType newTableType = new BTableType(this, tableType.isReadOnly()); this.addMember(newTableType); continue; - } else if (tableType.getConstrainedType() instanceof BMapType mapType) { + } else if (tableType.getConstrainedType() instanceof MapType mapType) { if (mapType.getConstrainedType() == unionType) { BMapType newMapType = new BMapType(this); BTableType newTableType = new BTableType(newMapType, @@ -540,4 +551,22 @@ public Optional getIntersectionType() { public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + public SemType createSemType() { + return memberTypes.stream().map(SemType::tryInto).reduce(Builder.getNeverType(), Core::union); + } + + @Override + protected boolean isDependentlyTypedInner(Set visited) { + return memberTypes.stream() + .filter(each -> each instanceof MayBeDependentType) + .anyMatch(type -> ((MayBeDependentType) type).isDependentlyTyped(visited)); + } + + @Override + public Optional acceptedTypeOf(Context cx) { + return Optional.of(memberTypes.stream().map(each -> ShapeAnalyzer.acceptedTypeOf(cx, each).orElseThrow()) + .reduce(Builder.getNeverType(), Core::union)); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BXmlType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BXmlType.java index 05e1cde6985c..45dd60c0ad10 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BXmlType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BXmlType.java @@ -20,11 +20,21 @@ import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.constants.TypeConstants; import io.ballerina.runtime.api.types.IntersectionType; +import io.ballerina.runtime.api.types.ParameterizedType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.XmlType; +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.XmlUtils; import io.ballerina.runtime.internal.values.ReadOnlyUtils; +import io.ballerina.runtime.internal.values.XmlComment; +import io.ballerina.runtime.internal.values.XmlItem; +import io.ballerina.runtime.internal.values.XmlPi; import io.ballerina.runtime.internal.values.XmlSequence; +import io.ballerina.runtime.internal.values.XmlText; import io.ballerina.runtime.internal.values.XmlValue; import java.util.Optional; @@ -35,10 +45,10 @@ * @since 0.995.0 */ @SuppressWarnings("unchecked") -public class BXmlType extends BType implements XmlType { +public class BXmlType extends BType implements XmlType, TypeWithShape { private final int tag; - public Type constraint; + public final Type constraint; private final boolean readonly; private IntersectionType immutableType; private IntersectionType intersectionType = null; @@ -63,6 +73,13 @@ public BXmlType(String typeName, Module pkg, int tag, boolean readonly) { this.constraint = null; } + public BXmlType(String typeName, Type constraint, Module pkg, int tag, boolean readonly) { + super(typeName, pkg, XmlValue.class); + this.tag = tag; + this.readonly = readonly; + this.constraint = constraint; + } + public BXmlType(String typeName, Type constraint, Module pkg, boolean readonly) { super(typeName, pkg, XmlValue.class); this.tag = TypeTags.XML_TAG; @@ -138,8 +155,103 @@ public Optional getIntersectionType() { return this.intersectionType == null ? Optional.empty() : Optional.of(this.intersectionType); } + @Override + public SemType createSemType() { + SemType semType; + if (constraint == null) { + semType = pickTopType(); + } else { + SemType contraintSemtype; + if (constraint instanceof ParameterizedType parameterizedType) { + contraintSemtype = tryInto(parameterizedType.getParamValueType()); + } else { + contraintSemtype = tryInto(constraint); + } + semType = XmlUtils.xmlSequence(contraintSemtype); + } + return isReadOnly() ? Core.intersect(Builder.getReadonlyType(), semType) : semType; + } + + private SemType pickTopType() { + return switch (tag) { + case TypeTags.XML_TAG -> Builder.getXmlType(); + case TypeTags.XML_ELEMENT_TAG -> Builder.getXmlElementType(); + case TypeTags.XML_COMMENT_TAG -> Builder.getXmlCommentType(); + case TypeTags.XML_PI_TAG -> Builder.getXmlPIType(); + case TypeTags.XML_TEXT_TAG -> Builder.getXmlTextType(); + default -> throw new IllegalStateException("Unexpected value: " + tag); + }; + } + @Override public void setIntersectionType(IntersectionType intersectionType) { this.intersectionType = intersectionType; } + + @Override + public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { + XmlValue xmlValue = (XmlValue) object; + if (!isReadOnly(xmlValue)) { + return Optional.of(getSemType()); + } + return readonlyShapeOf(object); + } + + @Override + public boolean couldInherentTypeBeDifferent() { + return true; + } + + @Override + public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Object object) { + return readonlyShapeOf(object).map(semType -> Core.intersect(semType, Builder.getReadonlyType())); + } + + @Override + public Optional acceptedTypeOf(Context cx) { + return Optional.of(getSemType()); + } + + private Optional readonlyShapeOf(Object object) { + if (object instanceof XmlSequence xmlSequence) { + // We represent xml as an empty sequence + var children = xmlSequence.getChildrenList(); + if (children.isEmpty()) { + return Optional.of(XmlUtils.xmlSingleton(XmlUtils.XML_PRIMITIVE_NEVER)); + } else if (children.size() == 1) { + // Not entirely sure if this is correct, but needed for passing tests + return readonlyShapeOf(children.get(0)); + } + return children.stream() + .map(this::readonlyShapeOf) + .filter(Optional::isPresent) + .map(Optional::get) + .reduce(Core::union) + .map(XmlUtils::xmlSequence); + } else if (object instanceof XmlText) { + // Text is inherently readonly + return Optional.of(Builder.getXmlTextType()); + } else if (object instanceof XmlItem xml) { + return getSemType(xml, Builder.getXmlElementType()); + } else if (object instanceof XmlComment xml) { + return getSemType(xml, Builder.getXmlCommentType()); + } else if (object instanceof XmlPi xml) { + return getSemType(xml, Builder.getXmlPIType()); + } + throw new IllegalArgumentException("Unexpected xml value: " + object); + } + + private static Optional getSemType(XmlValue xml, SemType baseType) { + if (isReadOnly(xml)) { + return Optional.of(Core.intersect(baseType, Builder.getReadonlyType())); + } + return Optional.of(baseType); + } + + private static boolean isReadOnly(XmlValue xmlValue) { + if (xmlValue instanceof XmlSequence || xmlValue instanceof XmlText) { + return true; + } + return xmlValue.getType().isReadOnly(); + } } 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..771ea177cfa5 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/DistinctIdSupplier.java @@ -0,0 +1,80 @@ +/* + * 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; + +/** + * A supplier that provides a list of distinct ids for a given type id set. + * + * @since 2201.11.0 + */ +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, TypeIdSet 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/MayBeDependentType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/MayBeDependentType.java new file mode 100644 index 000000000000..2a68601ce4bc --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/MayBeDependentType.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.internal.types; + +import java.util.Set; + +/** + * Represents a type that may be dependently typed. + * + * @since 2201.11.0 + */ +public interface MayBeDependentType { + + boolean isDependentlyTyped(); + + boolean isDependentlyTyped(Set visited); +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/ShapeSupplier.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/ShapeSupplier.java new file mode 100644 index 000000000000..999986483930 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/ShapeSupplier.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * Function that can be used to get the shape of a value. + * + * @since 2201.11.0 + */ +@FunctionalInterface +public interface ShapeSupplier { + + Optional get(Context cx, Object object); +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/TypeWithAcceptedType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/TypeWithAcceptedType.java new file mode 100644 index 000000000000..6dda26b4a8d6 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/TypeWithAcceptedType.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * Any {@code Type} that contains selectively immutable types must implement this interface. It represents the type + * against which {@code isLikeType} operation is performed. + * + * @since 2201.11.0 + */ +public interface TypeWithAcceptedType { + + Optional acceptedTypeOf(Context cx); +} 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..259062fc6896 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/TypeWithShape.java @@ -0,0 +1,42 @@ +/* + * 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; + +/** + * Types that are not basic types and have values whose shape could be different form the actual type (i.e. not handles) + * must implement this interface. Note that multiple values could share the same instance of TypeWithShape. Ideally + * different objects should be able to do their shape calculations in a non-blocking manner, even when they share the + * same instance of {@code TypeWithShape}. + * + * @since 2201.11.0 + */ +public interface TypeWithShape extends TypeWithAcceptedType { + + Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplierFn, Object object); + + Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Object object); + + boolean couldInherentTypeBeDifferent(); +} 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..104ed50d84ba --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/AllOrNothing.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.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.11.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..a93adceb286b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BBooleanSubType.java @@ -0,0 +1,166 @@ +/* + * 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 Boolean Sub Type. + * + * @since 2201.11.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() || other.isAll()) { + return ALL; + } + 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()) { + 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); + + 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..2192eec3e530 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BCellSubType.java @@ -0,0 +1,52 @@ +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.BddNode; +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 io.ballerina.runtime.api.types.semtype.TypeAtom; + +/** + * Represents a subtype of a Cell. + * + * @since 2201.11.0 + */ +public abstract sealed class BCellSubType extends SubType implements DelegatedSubType + permits BCellSubTypeImpl, BCellSubTypeSimple { + + public BCellSubType(boolean all, boolean nothing) { + super(all, nothing); + } + + public static BCellSubType createDelegate(SubType inner) { + Bdd bdd; + if (inner instanceof Bdd b) { + bdd = b; + } else if (inner instanceof BCellSubTypeImpl bCellImpl) { + bdd = bCellImpl.inner(); + } else if (inner instanceof BCellSubTypeSimple simple) { + return simple; + } else { + throw new IllegalArgumentException("Unexpected inner type"); + } + if (!(bdd instanceof BddNode bddNode && bddNode.isSimple())) { + return new BCellSubTypeImpl(bdd); + } + Atom atom = bddNode.atom(); + if (!(atom instanceof TypeAtom typeAtom)) { + return new BCellSubTypeImpl(bdd); + } + CellAtomicType atomicType = (CellAtomicType) typeAtom.atomicType(); + SemType ty = atomicType.ty(); + // We have special logic when it comes to handling undef that needs to be updated to deal with simple cell + // TODO: probably we can also handle immutable cells as well + if (Core.containsBasicType(ty, Builder.getUndefType()) || ty.some() != 0 || + atomicType.mut() != CellAtomicType.CellMutability.CELL_MUT_LIMITED) { + return new BCellSubTypeImpl(bdd); + } + return new BCellSubTypeSimple(ty, bddNode); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BCellSubTypeImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BCellSubTypeImpl.java new file mode 100644 index 000000000000..0335d6513051 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BCellSubTypeImpl.java @@ -0,0 +1,186 @@ +/* + * 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.SemType; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.Objects; +import java.util.function.Predicate; + +/** + * Runtime representation of CellSubType. + * + * @since 2201.11.0 + */ +final class BCellSubTypeImpl extends BCellSubType implements DelegatedSubType { + + private final Bdd inner; + + BCellSubTypeImpl(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + @Override + public SubType union(SubType other) { + if (other instanceof BCellSubType otherCell) { + return createDelegate(inner.union(otherCell.inner())); + } + throw new IllegalArgumentException("union of different subtypes"); + } + + @Override + public SubType intersect(SubType other) { + if (other instanceof BCellSubType otherCell) { + return createDelegate(inner.intersect(otherCell.inner())); + } + throw new IllegalArgumentException("intersect of different subtypes"); + } + + @Override + public SubType complement() { + return createDelegate(inner.complement()); + } + + @Override + public boolean isEmpty(Context cx) { + return Bdd.bddEvery(cx, inner, BCellSubTypeImpl::cellFormulaIsEmpty); + } + + @Override + public SubType diff(SubType other) { + if (other instanceof BCellSubType otherCell) { + return createDelegate(inner.diff(otherCell.inner())); + } + throw new IllegalArgumentException("diff of different subtypes"); + + } + + @Override + public SubTypeData data() { + throw new IllegalStateException("unimplemented"); + } + + private static boolean cellFormulaIsEmpty(Context cx, Conjunction posList, Conjunction negList) { + CellAtomicType combined; + if (posList == null) { + combined = CellAtomicType.from(Builder.getValType(), CellAtomicType.CellMutability.CELL_MUT_UNLIMITED); + } else { + combined = CellAtomicType.cellAtomType(posList.atom()); + Conjunction p = posList.next(); + while (p != null) { + combined = CellAtomicType.intersectCellAtomicType(combined, CellAtomicType.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 (CellAtomicType.cellAtomType(neg.atom()).mut() == CellAtomicType.CellMutability.CELL_MUT_LIMITED && + Core.isSameType(cx, Builder.getValType(), CellAtomicType.cellAtomType(neg.atom()).ty())) { + return false; + } + neg = neg.next(); + } + SemType negListUnionResult = filteredCellListUnion(negList, + conjunction -> CellAtomicType.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 = CellAtomicType.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.getNeverType(); + Conjunction neg = negList; + while (neg != null) { + if (predicate.test(neg)) { + negUnion = Core.union(negUnion, CellAtomicType.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 BCellSubTypeImpl 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/BCellSubTypeSimple.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BCellSubTypeSimple.java new file mode 100644 index 000000000000..8166bf862624 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BCellSubTypeSimple.java @@ -0,0 +1,137 @@ +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.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.Env; +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 io.ballerina.runtime.internal.TypeChecker; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; + +/** + * Simplified representation of cell if the type is only basic type union and mutability is limited. + * + * @since 2201.11.0 + */ +final class BCellSubTypeSimple extends BCellSubType implements DelegatedSubType { + + private final List pos; + private final List neg; + private BddNode inner; + + BCellSubTypeSimple(SemType type) { + super(type.all() == BasicTypeCode.VT_MASK, type.all() == 0); + assert type.some() == 0; + this.pos = List.of(type); + this.neg = List.of(); + } + + BCellSubTypeSimple(SemType type, BddNode bddNode) { + this(type); + inner = bddNode; + } + + private BCellSubTypeSimple(List pos, List neg) { + super(false, false); + this.pos = pos; + this.neg = neg; + } + + @Override + public SubType union(SubType other) { + if (other instanceof BCellSubTypeSimple simple) { + // P1\N1 U P2\N2 = (P1 U P2)\(N1 U N2) + List combinedPos = Stream.concat(pos.stream(), simple.pos.stream()).toList(); + List combinedNeg = Stream.concat(neg.stream(), simple.neg.stream()).toList(); + return new BCellSubTypeSimple(combinedPos, combinedNeg); + } else if (other instanceof BCellSubTypeImpl complex) { + return createDelegate(inner().union(complex.inner())); + } + throw new IllegalArgumentException("union of different subtypes"); + } + + @Override + public SubType intersect(SubType other) { + if (other instanceof BCellSubTypeSimple simple) { + // P1\N1 ∩ P2\N2 = (P1 ∩ P2)\(N1 U N2) + SemType pos = + Stream.concat(this.pos.stream(), simple.pos.stream()).reduce(Builder.getValType(), Core::intersect); + List neg = Stream.concat(this.neg.stream(), simple.neg.stream()).toList(); + return new BCellSubTypeSimple(List.of(pos), neg); + } else if (other instanceof BCellSubTypeImpl complex) { + return createDelegate(inner().intersect(complex.inner())); + } + throw new IllegalArgumentException("intersection of different subtypes"); + } + + @Override + public SubType complement() { + return new BCellSubTypeSimple(neg, pos); + } + + @Override + public boolean isEmpty(Context cx) { + if (pos.isEmpty()) { + return true; + } + SemType posUnion = pos.stream().reduce(Builder.getNeverType(), Core::union); + if (neg.isEmpty()) { + return Core.isEmpty(cx, posUnion); + } + return neg.stream().anyMatch(neg -> Core.isEmpty(cx, Core.diff(posUnion, neg))); + } + + @Override + public SubTypeData data() { + throw new IllegalStateException("unimplemented"); + } + + @Override + public SubType inner() { + if (inner != null) { + return inner; + } + Env env = TypeChecker.getEnv(); + Optional posBdd = + pos.stream().map(semType -> fromSemType(env, semType)).reduce((acum, bdd) -> (Bdd) acum.union(bdd)); + if (posBdd.isEmpty()) { + return BddAllOrNothing.NOTHING; + } + Optional negBdd = + neg.stream().map(semType -> fromSemType(env, semType)).reduce((acum, bdd) -> (Bdd) acum.union(bdd)); + if (negBdd.isEmpty()) { + return posBdd.get(); + } + return posBdd.get().diff(negBdd.get()); + } + + private static Bdd fromSemType(Env env, SemType type) { + CellAtomicType atomicCell = CellAtomicType.from(type, CellAtomicType.CellMutability.CELL_MUT_LIMITED); + TypeAtom atom = env.cellAtom(atomicCell); + return bddAtom(atom); + } + + @Override + public int hashCode() { + return Stream.concat(pos.stream(), neg.stream()).map(SemType::hashCode).reduce(0, Integer::sum); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof BCellSubTypeSimple other)) { + return false; + } + return pos.equals(other.pos) && neg.equals(other.neg); + } +} 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..03d2902e17db --- /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.11.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 + public boolean allowed() { + return allowed; + } + + @Override + public 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..5045122f9a2a --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BErrorSubType.java @@ -0,0 +1,117 @@ +/* + * 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; + +/** + * Runtime representation of a subtype of error type. + * + * @since 2201.11.0 + */ +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(Builder.getBddSubtypeRo().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.getBddSubtypeRo()) : 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..333e986ae9be --- /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.11.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 + public boolean allowed() { + return allowed; + } + + @Override + public 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..cad6312b7744 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BFunctionSubType.java @@ -0,0 +1,172 @@ +/* + * 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.SemType; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.Objects; + +import static io.ballerina.runtime.api.types.semtype.Bdd.bddEvery; + +/** + * Runtime representation of a subtype of function type. + * + * @since 2201.11.0 + */ +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, 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.getValType(); + } + 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.getNeverType(); + } + 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.getNeverType(); + } + 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/BFutureSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BFutureSubType.java new file mode 100644 index 000000000000..c9389cff9671 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BFutureSubType.java @@ -0,0 +1,103 @@ +/* + * 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.bddEvery; + +/** + * Runtime representation of a subtype of future type. + * + * @since 2201.11.0 + */ +public final class BFutureSubType extends SubType implements DelegatedSubType { + + private final Bdd inner; + + private BFutureSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + public static BFutureSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BFutureSubType(bdd); + } else if (inner.isAll() || inner.isNothing()) { + throw new IllegalStateException("unimplemented"); + } else if (inner instanceof BFutureSubType other) { + return new BFutureSubType(other.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BFutureSubType otherFuture)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherFuture.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BFutureSubType otherFuture)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.intersect(otherFuture.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, BMappingSubType::mappingFormulaIsEmpty), inner); + } + + @Override + public SubTypeData data() { + throw new UnsupportedOperationException("Method not implemented"); + } + + @Override + public SubType inner() { + throw new UnsupportedOperationException("Method not implemented"); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof BFutureSubType other)) { + return false; + } + return Objects.equals(inner, other.inner); + } + + @Override + public int hashCode() { + return Objects.hash(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..30e123d338fe --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BIntSubType.java @@ -0,0 +1,336 @@ +/* + * 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 a int subtype. + * + * @since 2201.11.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) { + assert min < max : "Invalid range"; + 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); + } + + public 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 List values() { + List values = new ArrayList<>(); + for (Range range : ranges) { + for (long i = range.min; i <= range.max; i++) { + values.add(i); + } + } + return values; + } + + 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..adcd59d67c89 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BListProj.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +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.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.getRoCellContaining; +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.11.0 + */ +public final class BListProj { + + private BListProj() { + } + + public static SemType listProjInnerVal(Context cx, SemType t, SemType k) { + if (t.some() == 0) { + return t == Builder.getListType() ? Builder.getValType() : Builder.getNeverType(); + } else { + SubTypeData keyData = Core.intSubtype(k); + if (isNothingSubtype(keyData)) { + return Builder.getNeverType(); + } + 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.getNeverType(); + } 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 = Builder.getRwCellContaining(cx.env, union(Builder.getValType(), Builder.getUndefType())); + } 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.getNeverType(); + } + members = intersected.first(); + rest = intersected.second(); + } + } + if (fixedArrayAnyEmpty(cx, members)) { + return Builder.getNeverType(); + } + // Ensure that we can use isNever on rest in listInhabited + if (!isNever(cellInnerVal(rest)) && isEmpty(cx, rest)) { + rest = getRoCellContaining(cx.env, Builder.getNeverType()); + } + } + 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.getNeverType(); + 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] = Builder.getRwCellContaining(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..bb3bbec3769a --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BListSubType.java @@ -0,0 +1,462 @@ +/* + * 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.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.cellInner; +import static io.ballerina.runtime.api.types.semtype.Core.cellInnerVal; +import static io.ballerina.runtime.api.types.semtype.Core.getCellContainingInnerVal; +import static io.ballerina.runtime.api.types.semtype.Core.intersectCellMemberSemTypes; +import static io.ballerina.runtime.internal.types.semtype.BIntSubType.intSubtypeContains; + +/** + * Runtime representation of a subtype of list type. + * + * @since 2201.11.0 + */ +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, BListSubType::listFormulaIsEmpty), inner); + } + + static boolean listFormulaIsEmpty(Context cx, Conjunction pos, Conjunction neg) { + FixedLengthArray members; + SemType rest; + if (pos == null) { + ListAtomicType atom = Builder.getListAtomicInner(); + 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 = getCellContainingInnerVal(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] = + intersectCellMemberSemTypes(env, listMemberAt(members1, rest1, i), + listMemberAt(members2, rest2, i)); + } + return Pair.from(FixedLengthArray.from(initial, + Integer.max(members1.fixedLength(), members2.fixedLength())), + intersectCellMemberSemTypes(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.getNeverType(); + } 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.getUndefType()); + } + + 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.getNeverType(); + 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() { + return inner(); + } + + @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..94f344aa1e85 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BMappingProj.java @@ -0,0 +1,122 @@ +/* + * 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.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; + +/** + * Utility class for doing mapping type projection. + * + * @since 2201.11.0 + */ +public final class BMappingProj { + + private BMappingProj() { + } + + public static SemType mappingMemberTypeInnerVal(Context cx, SemType t, SemType k) { + return diff(mappingMemberTypeInner(cx, t, k), Builder.getUndefType()); + } + + // 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. + public static SemType mappingMemberTypeInner(Context cx, SemType t, SemType k) { + if (t.some() == 0) { + return (t.all() & Builder.getMappingType().all()) != 0 ? Builder.getValType() : Builder.getUndefType(); + } else { + SubTypeData keyData = stringSubtype(k); + if (isNothingSubtype(keyData)) { + return Builder.getUndefType(); + } + return bddMappingMemberTypeInner(cx, (Bdd) getComplexSubtypeData(t, BT_MAPPING), keyData, + Builder.getInnerType()); + } + } + + static SemType bddMappingMemberTypeInner(Context cx, Bdd b, SubTypeData key, SemType accum) { + if (b instanceof BddAllOrNothing allOrNothing) { + return allOrNothing.isAll() ? accum : Builder.getNeverType(); + } 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.getUndefType() : 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..bc56809a3c66 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BMappingSubType.java @@ -0,0 +1,221 @@ +/* + * 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.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; + +/** + * Runtime representation of a subtype of mapping type. + * + * @since 2201.11.0 + */ +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 otherMapping)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherMapping.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BMappingSubType otherMapping)) { + throw new IllegalArgumentException("intersect of different subtypes"); + } + return createDelegate(inner.intersect(otherMapping.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, BMappingSubType::mappingFormulaIsEmpty), inner); + } + + static boolean mappingFormulaIsEmpty(Context cx, Conjunction posList, Conjunction negList) { + MappingAtomicType combined; + if (posList == null) { + combined = Builder.getMappingAtomicInner(); + } 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 intersect = Core.intersect(fieldPair.type1(), fieldPair.type2()); + // if types of at least one field are disjoint, the neg atom will not contribute to the next iteration. + // Therefore, we can skip the current neg atom. + // i.e. if we have isEmpty(T1 & S1) or isEmpty(T2 & S2) then, + // record { T1 f1; T2 f2; } / record { S1 f1; S2 f2; } = record { T1 f1; T2 f2; } + if (Core.isEmpty(cx, intersect)) { + return mappingInhabited(cx, pos, negList.next()); + } + + 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..de6516a4086a --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BObjectSubType.java @@ -0,0 +1,113 @@ +/* + * 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; + +/** + * Runtime representation of a subtype of object type. + * + * @since 2201.11.0 + */ +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/BStreamSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BStreamSubType.java new file mode 100644 index 000000000000..c666b54ce417 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BStreamSubType.java @@ -0,0 +1,95 @@ +/* + * 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 static io.ballerina.runtime.api.types.semtype.Bdd.bddEvery; + +/** + * Runtime representation of a subtype of stream type. + * + * @since 2201.11.0 + */ +public class BStreamSubType extends SubType implements DelegatedSubType { + + public final Bdd inner; + + private BStreamSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + public static BStreamSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BStreamSubType(bdd); + } else if (inner.isAll() || inner.isNothing()) { + throw new IllegalStateException("unimplemented"); + } else if (inner instanceof BStreamSubType bStreamSubType) { + return new BStreamSubType(bStreamSubType.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BStreamSubType otherStream)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherStream.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BStreamSubType otherStream)) { + throw new IllegalArgumentException("intersect of different subtypes"); + } + return createDelegate(inner.intersect(otherStream.inner)); + } + + @Override + public SubType complement() { + return createDelegate(Builder.getListSubtypeTwoElement().diff(inner)); + } + + @Override + public boolean isEmpty(Context cx) { + Bdd b = inner; + // The goal of this is to ensure that listSubtypeIsEmpty call beneath does + // not get an empty posList, because it will interpret that + // as `[any|error...]` rather than `[any|error, any|error]`. + b = b.posMaybeEmpty() ? (Bdd) b.intersect(Builder.getListSubtypeTwoElement()) : b; + return cx.memoSubtypeIsEmpty(cx.listMemo, + (context, bdd) -> bddEvery(context, bdd, BListSubType::listFormulaIsEmpty), b); + } + + @Override + public SubTypeData data() { + return inner(); + } + + @Override + public Bdd inner() { + return 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..0527874f61f1 --- /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 subtype of string type. + * + * @since 2201.11.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 + public boolean allowed() { + return allowed; + } + + @Override + public String[] values() { + return values; + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BTableSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BTableSubType.java new file mode 100644 index 000000000000..91243d618228 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BTableSubType.java @@ -0,0 +1,110 @@ +/* + * 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.bddEvery; + +/** + * Represents the subtype of a table type. + * + * @since 2201.11.0 + */ +public final class BTableSubType extends SubType implements DelegatedSubType { + + private final Bdd inner; + + private BTableSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + public static BTableSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BTableSubType(bdd); + } else if (inner.isAll() || inner.isNothing()) { + throw new IllegalStateException("unimplemented"); + } else if (inner instanceof BTableSubType other) { + return new BTableSubType(other.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BTableSubType otherTable)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherTable.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BTableSubType otherTable)) { + throw new IllegalArgumentException("intersect of different subtypes"); + } + return createDelegate(inner.intersect(otherTable.inner)); + } + + @Override + public SubType complement() { + return createDelegate(Builder.getListSubtypeThreeElement().diff(inner)); + } + + @Override + public boolean isEmpty(Context cx) { + Bdd b = inner; + // The goal of this is to ensure that listSubtypeIsEmpty call beneath does + // not get an empty posList, because it will interpret that + // as `(any|error)[]` rather than `[(map)[], any|error, any|error]`. + b = b.posMaybeEmpty() ? (Bdd) b.intersect(Builder.getListSubtypeThreeElement()) : b; + return cx.memoSubtypeIsEmpty(cx.listMemo, + (context, bdd) -> bddEvery(context, bdd, BListSubType::listFormulaIsEmpty), b); + } + + @Override + public SubTypeData data() { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public SubType inner() { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof BTableSubType other)) { + return false; + } + return inner.equals(other.inner); + } + + @Override + public int hashCode() { + return Objects.hash(inner); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BTypedescSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BTypedescSubType.java new file mode 100644 index 000000000000..b8b0e91212a3 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BTypedescSubType.java @@ -0,0 +1,113 @@ +/* + * 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; + +/** + * Represents the subtype of a typedesc type. + * + * @since 2201.11.0 + */ +public class BTypedescSubType extends SubType implements DelegatedSubType { + + private final Bdd inner; + + private BTypedescSubType(Bdd inner) { + super(inner.isAll(), inner.isNothing()); + this.inner = inner; + } + + public static BTypedescSubType createDelegate(SubType inner) { + if (inner instanceof Bdd bdd) { + return new BTypedescSubType(bdd); + } else if (inner.isAll() || inner.isNothing()) { + throw new IllegalStateException("unimplemented"); + } else if (inner instanceof BTypedescSubType other) { + return new BTypedescSubType(other.inner); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public SubType union(SubType other) { + if (!(other instanceof BTypedescSubType otherTypedesc)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.union(otherTypedesc.inner)); + } + + @Override + public SubType intersect(SubType other) { + if (!(other instanceof BTypedescSubType otherTypedesc)) { + throw new IllegalArgumentException("union of different subtypes"); + } + return createDelegate(inner.intersect(otherTypedesc.inner)); + } + + @Override + public SubType complement() { + return createDelegate(Builder.getBddSubtypeRo().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.getBddSubtypeRo()) : b; + return cx.memoSubtypeIsEmpty(cx.mappingMemo, BTypedescSubType::typedescBddIsEmpty, b); + } + + private static boolean typedescBddIsEmpty(Context cx, Bdd b) { + return bddEveryPositive(cx, b, null, null, BMappingSubType::mappingFormulaIsEmpty); + } + + @Override + public SubTypeData data() { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public SubType inner() { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof BTypedescSubType other)) { + return false; + } + return Objects.equals(inner, other.inner); + } + + @Override + public int hashCode() { + return Objects.hash(inner); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BXmlSubType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BXmlSubType.java new file mode 100644 index 000000000000..35479680eb62 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BXmlSubType.java @@ -0,0 +1,165 @@ +/* + * 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.Conjunction; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.RecAtom; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.Objects; + +/** + * Represents the subtype of an XML type. + * + * @since 2201.11.0 + */ +public class BXmlSubType extends SubType implements DelegatedSubType { + + public final Bdd inner; + private final int primitives; + + private BXmlSubType(Bdd inner, int primitives) { + super(false, false); + this.inner = inner; + this.primitives = primitives; + } + + public static BXmlSubType createDelegate(int primitives, SubType inner) { + if (inner instanceof Bdd bdd) { + return new BXmlSubType(bdd, primitives); + } else if (inner instanceof BXmlSubType bXml) { + return new BXmlSubType(bXml.inner, primitives); + } + throw new IllegalArgumentException("Unexpected inner type"); + } + + @Override + public SubType union(SubType other) { + BXmlSubType otherXml = (BXmlSubType) other; + int primitives = this.primitives() | otherXml.primitives(); + return createDelegate(primitives, inner.union(otherXml.inner)); + } + + @Override + public SubType intersect(SubType other) { + BXmlSubType otherXml = (BXmlSubType) other; + int primitives = this.primitives() & otherXml.primitives(); + return createDelegate(primitives, inner.intersect(otherXml.inner)); + } + + @Override + public SubType diff(SubType other) { + BXmlSubType otherXml = (BXmlSubType) other; + return diff(this, otherXml); + } + + private static SubType diff(BXmlSubType st1, BXmlSubType st2) { + int primitives = st1.primitives() & ~st2.primitives(); + return createDelegate(primitives, st1.inner.diff(st2.inner)); + } + + @Override + public SubType complement() { + return diff((BXmlSubType) XmlUtils.XML_SUBTYPE_TOP, this); + } + + @Override + public boolean isEmpty(Context cx) { + if (primitives() != 0) { + return false; + } + return xmlBddEmpty(cx); + } + + private boolean xmlBddEmpty(Context cx) { + return Bdd.bddEvery(cx, inner, BXmlSubType::xmlFormulaIsEmpty); + } + + private static boolean xmlFormulaIsEmpty(Context cx, Conjunction pos, Conjunction neg) { + int allPosBits = collectAllPrimitives(pos) & XmlUtils.XML_PRIMITIVE_ALL_MASK; + return xmlHasTotalNegative(allPosBits, neg); + } + + private static boolean xmlHasTotalNegative(int allPosBits, Conjunction conjunction) { + if (allPosBits == 0) { + return true; + } + Conjunction n = conjunction; + while (n != null) { + if ((allPosBits & ~getIndex(n)) == 0) { + return true; + } + n = n.next(); + } + return false; + } + + private static int collectAllPrimitives(Conjunction conjunction) { + int bits = 0; + Conjunction current = conjunction; + while (current != null) { + bits &= getIndex(current); + current = current.next(); + } + return bits; + } + + private static int getIndex(Conjunction conjunction) { + var atom = conjunction.atom(); + assert atom instanceof RecAtom; + return atom.index(); + } + + @Override + public SubTypeData data() { + return this; + } + + @Override + public SubType inner() { + return this; + } + + int primitives() { + return primitives; + } + + Bdd bdd() { + return inner; + } + + @Override + public int hashCode() { + return Objects.hash(inner, primitives); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BXmlSubType other)) { + return false; + } + return Objects.equals(bdd(), other.bdd()) && primitives() == other.primitives(); + } + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BddMemo.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BddMemo.java new file mode 100644 index 000000000000..739117d179f5 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/BddMemo.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.types.semtype; + +import java.util.Objects; +import java.util.Optional; + +/** + * Represents the memoization emptiness of a BDD used in Context. + * + * @since 2201.11.0 + */ +public final class BddMemo { + + public Status isEmpty; + + public BddMemo() { + this.isEmpty = Status.NULL; + } + + public enum Status { + // We know where this BDD is empty or not + TRUE, + FALSE, + // There is some recursive part in this type + LOOP, + CYCLIC, + // We are in the process of determining if this BDD is empty or not + PROVISIONAL, + // We just initialized the node, treated to be same as not having a memo + 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); + } + + public Optional isEmpty() { + return switch (isEmpty) { + // Cyclic types are empty because we define types inductively + case TRUE, CYCLIC -> Optional.of(true); + case FALSE -> Optional.of(false); + case LOOP, PROVISIONAL -> { + // If it was provisional we came from a back edge + // Again we treat the loop part as empty + isEmpty = Status.LOOP; + yield Optional.of(true); + } + case NULL -> Optional.empty(); + }; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/CellAtomicType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/CellAtomicType.java new file mode 100644 index 000000000000..8e75f75af7b6 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/CellAtomicType.java @@ -0,0 +1,88 @@ +/* + * 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.AtomicType; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.TypeAtom; + +import java.util.HashMap; +import java.util.Map; + +/** + * CellAtomicType node. + * + * @param ty Type "wrapped" by this cell + * @param mut Mutability of the cell + * @since 2201.11.0 + */ +public record CellAtomicType(SemType ty, CellMutability mut) implements AtomicType { + + public CellAtomicType { + assert ty != null; + } + + public static CellAtomicType from(SemType ty, CellMutability mut) { + return CellAtomCache.get(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 CellAtomicType.from(ty, mut); + } + + private static CellMutability min(CellMutability m1, + CellMutability m2) { + return m1.compareTo(m2) <= 0 ? m1 : m2; + } + + public static CellAtomicType cellAtomType(Atom atom) { + return (CellAtomicType) ((TypeAtom) atom).atomicType(); + } + + public enum CellMutability { + CELL_MUT_NONE, + CELL_MUT_LIMITED, + CELL_MUT_UNLIMITED + } + + private static final class CellAtomCache { + + private static final Map NONE_CACHE = new HashMap<>(); + private static final Map LIMITED_CACHE = new HashMap<>(); + private static final Map UNLIMITED_CACHE = new HashMap<>(); + + private static CellAtomicType get(SemType semType, CellMutability mut) { + if (semType.some() != 0) { + return new CellAtomicType(semType, mut); + } + int key = semType.all(); + return switch (mut) { + case CELL_MUT_NONE -> NONE_CACHE.computeIfAbsent(key, (ignored) -> new CellAtomicType(semType, mut)); + case CELL_MUT_LIMITED -> + LIMITED_CACHE.computeIfAbsent(key, (ignored) -> new CellAtomicType(semType, mut)); + case CELL_MUT_UNLIMITED -> + UNLIMITED_CACHE.computeIfAbsent(key, (ignored) -> new CellAtomicType(semType, mut)); + }; + } + } +} 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..8d057aa5e4ef --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Common.java @@ -0,0 +1,54 @@ +/* + * 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; + +/** + * Utility class for various operations related to semantic types. + * + * @since 2201.11.0 + */ +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/DefinitionContainer.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DefinitionContainer.java new file mode 100644 index 000000000000..2b4373a2de5e --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DefinitionContainer.java @@ -0,0 +1,111 @@ +package io.ballerina.runtime.internal.types.semtype; + +import io.ballerina.runtime.api.types.semtype.Definition; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; + +/** + * Container used to maintain concurrency invariants when creating a potentially recursive semtype. + * + * It maintains fallowing invariants. + * 1. When the type is being defined only the thread that is defining the type may proceed + * 2. After definition is completed any number of threads may proceed concurrently In order to achieve this container + * has three phases (init, defining, defined). At init phase (that is no definition has been set) any number of threads + * may proceed concurrently. When a thread sets a definition {@code setDefinition} container enters the defining phase. + * In that phase only that thread may continue in {@code getSemType} method (this is to allow for recursive type + * definitions). Container registers with the {@code Definition} using {@code registerContainer} method. When the + * {@code Definition} has been defined (ie. {@code Env} has an atom corresponding to the definition) it must notify the + * container using {@code definitionUpdated} method. At this point container moves to defined phase allowing concurrent + * access to {@code getSemType}. + * + * @param type of the definition + * @since 2201.11.0 + */ +public class DefinitionContainer { + + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + private volatile E definition; + + private final ReentrantLock recTypeLock = new ReentrantLock(); + private volatile boolean isDefining = false; + + public boolean isDefinitionReady() { + try { + rwLock.readLock().lock(); + return definition != null; + } finally { + rwLock.readLock().unlock(); + } + } + + public SemType getSemType(Env env) { + try { + rwLock.readLock().lock(); + // We don't need this check to be synchronized since {@code trySetDefinition} will hold the write lock until + // it completes, So isDefining should always be at a consistent state + if (isDefining) { + // This should prevent threads other than the defining thread to access the rec atom. + recTypeLock.lock(); + } + return definition.getSemType(env); + } finally { + rwLock.readLock().unlock(); + } + } + + public DefinitionUpdateResult trySetDefinition(Supplier supplier) { + try { + rwLock.writeLock().lock(); + boolean updated; + E newDefinition; + if (this.definition != null) { + updated = false; + newDefinition = null; + } else { + updated = true; + newDefinition = supplier.get(); + newDefinition.registerContainer(this); + this.recTypeLock.lock(); + isDefining = true; + this.definition = newDefinition; + } + return new DefinitionUpdateResult<>(newDefinition, updated); + } finally { + rwLock.writeLock().unlock(); + } + } + + public void clear() { + try { + rwLock.writeLock().lock(); + // This shouldn't happen because defining thread should hold the lock. + assert !isDefining; + this.definition = null; + } finally { + rwLock.writeLock().unlock(); + } + } + + public void definitionUpdated() { + recTypeLock.unlock(); + isDefining = false; + } + + /** + * Result of trying to update the definition. + * + * @param Type of the definition + * @param updated If update was successful. If this failed you must get the semtype using the {@code getSemType} + * method of the container + * @param definition If update was successful this will be the new definition. Otherwise, this will be null + * @since 2201.11.0 + */ + public record DefinitionUpdateResult(E definition, boolean updated) { + + } +} 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..6f798ca4d07c --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DelegatedSubType.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * Represents the subtype implemented by BDDs. + * + * @since 2201.11.0 + */ +public interface DelegatedSubType extends SubTypeData { + + SubType 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..4f8429d51f1f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/EnumerableSubtypeData.java @@ -0,0 +1,181 @@ +/* + * + * 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.11.0 + */ +public abstract class EnumerableSubtypeData> { + + public abstract boolean allowed(); + + public 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..870fae6f4e7a --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ErrorUtils.java @@ -0,0 +1,65 @@ +/* + * 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; + +/** + * Utility methods for creating error types. + * + * @since 2201.11.0 + */ +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.getErrorType(); + } else if (data == AllOrNothing.NOTHING) { + return Builder.getNeverType(); + } + + assert data instanceof Bdd; + SubType sd = ((Bdd) data).intersect(Builder.getBddSubtypeRo()); + if (sd.equals(Builder.getBddSubtypeRo())) { + return Builder.getErrorType(); + } + 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..8284a390a38b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FixedLengthArray.java @@ -0,0 +1,123 @@ +/* + * 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; + +/** + * Represents required member part of a list atom. + * + * @since 2201.11.0 + */ +public final class FixedLengthArray { + + // We have a separate fixedLength so types such as {@code byte[500]} don't need to store 500 elements + // in {@code initial}. + 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; + } + + public 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/FunctionAtomicType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionAtomicType.java new file mode 100644 index 000000000000..349ca151de59 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionAtomicType.java @@ -0,0 +1,34 @@ +/* + * 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.AtomicType; +import io.ballerina.runtime.api.types.semtype.SemType; + +/** + * Represents a function atomic type. + * + * @param paramType function parameters. This is a list type + * @param retType return type + * @param qualifiers function qualifiers. This is a list type + * @since 2201.11.0 + */ +public record FunctionAtomicType(SemType paramType, SemType retType, SemType qualifiers) implements AtomicType { + +} 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..4878cbe1f717 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionDefinition.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.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.Definition; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.RecAtom; +import io.ballerina.runtime.api.types.semtype.SemType; + +/** + * {@code Definition} used to create function subtypes. + * + * @since 2201.11.0 + */ +public class FunctionDefinition extends Definition { + + private volatile RecAtom rec; + private volatile 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); + } + SemType semType = this.createSemType(atom); + notifyContainer(); + return semType; + } + +} 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..0b90e9bad41d --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionQualifiers.java @@ -0,0 +1,77 @@ +/* + * 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.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +/** + * Represents the qualifiers of a function. + * + * @since 2201.11.0 + */ +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.getBooleanConst(true) : Builder.getBooleanType(), + transactional ? Builder.getBooleanType() : Builder.getBooleanConst(false) + }; + semType = ld.defineListTypeWrapped(env, members, 2, Builder.getNeverType(), + 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/FutureUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FutureUtils.java new file mode 100644 index 000000000000..8c927ac0b442 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FutureUtils.java @@ -0,0 +1,52 @@ +/* + * 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.Builder; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.types.semtype.Core.createBasicSemType; +import static io.ballerina.runtime.api.types.semtype.Core.subTypeData; + +/** + * Utility methods for creating future types. + * + * @since 2201.11.0 + */ +public final class FutureUtils { + + private static final MappingDefinition.Field[] EMPTY_FIELDS = new MappingDefinition.Field[0]; + + private FutureUtils() { + } + + public static SemType futureContaining(Env env, SemType constraint) { + if (constraint == Builder.getValType()) { + return Builder.getFutureType(); + } + MappingDefinition md = new MappingDefinition(); + SemType mappingType = md.defineMappingTypeWrapped(env, EMPTY_FIELDS, constraint, + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + Bdd bdd = (Bdd) subTypeData(mappingType, BasicTypeCode.BT_MAPPING); + return createBasicSemType(BasicTypeCode.BT_FUTURE, bdd); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ImmutableSemType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ImmutableSemType.java new file mode 100644 index 000000000000..d63513b1b31f --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ImmutableSemType.java @@ -0,0 +1,90 @@ +/* + * 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.SemType; +import io.ballerina.runtime.api.types.semtype.SubType; +import io.ballerina.runtime.internal.types.BSemTypeWrapper; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Runtime representation of an immutable semtype. + * + * @since 2201.11.0 + */ +public abstract sealed class ImmutableSemType extends SemType permits BSemTypeWrapper { + + private static final SubType[] EMPTY_SUBTYPE_DATA = new SubType[0]; + + private Integer hashCode; + + ImmutableSemType(int all, int some, SubType[] subTypeData) { + super(all, some, subTypeData); + } + + ImmutableSemType(int all) { + this(all, 0, EMPTY_SUBTYPE_DATA); + } + + protected ImmutableSemType(SemType semType) { + this(semType.all(), semType.some(), semType.subTypeData()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ImmutableSemType semType)) { + return false; + } + return all() == semType.all() && some() == semType.some() && + Objects.deepEquals(this.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())); + } + + @Override + protected void setAll(int all) { + throw new UnsupportedOperationException("Immutable semtypes cannot be modified"); + } + + @Override + protected void setSome(int some, SubType[] subTypeData) { + throw new UnsupportedOperationException("Immutable semtypes cannot be modified"); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListAtomicType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListAtomicType.java new file mode 100644 index 000000000000..5631bbc55036 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListAtomicType.java @@ -0,0 +1,33 @@ +/* + * 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.AtomicType; +import io.ballerina.runtime.api.types.semtype.SemType; + +/** + * Represent list atomic type. + * + * @param members required member types of the list + * @param rest rest of member type of the list + * @since 2201.11.0 + */ +public record ListAtomicType(FixedLengthArray members, SemType rest) implements AtomicType { + +} 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..58bd4674453e --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListDefinition.java @@ -0,0 +1,91 @@ +/* + * 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.Builder; +import io.ballerina.runtime.api.types.semtype.Definition; +import io.ballerina.runtime.api.types.semtype.Env; +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.getUndefType; +import static io.ballerina.runtime.api.types.semtype.Core.isNever; +import static io.ballerina.runtime.api.types.semtype.Core.union; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; + +/** + * {@code Definition} used to create a list type. + * + * @since 2201.11.0 + */ +public class ListDefinition extends Definition { + + private volatile RecAtom rec = null; + private volatile 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] = Builder.getCellContaining(env, initial[i], mut); + } + SemType restCell = + Builder.getCellContaining(env, union(rest, getUndefType()), isNever(rest) ? CELL_MUT_NONE : mut); + SemType semType = define(env, initialCells, fixedLength, restCell); + notifyContainer(); + return semType; + } + + 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/MappingAtomicType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingAtomicType.java new file mode 100644 index 000000000000..9ad1916a6637 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingAtomicType.java @@ -0,0 +1,65 @@ +/* + * 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.AtomicType; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.FieldPair; +import io.ballerina.runtime.api.types.semtype.FieldPairs; +import io.ballerina.runtime.api.types.semtype.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.intersectCellMemberSemTypes; +import static io.ballerina.runtime.api.types.semtype.Core.isNever; + +/** + * Represent mapping atomic type. + * + * @param names required member names of the mapping + * @param types required member types of the mapping + * @param rest rest of member type of the mapping + * @since 2201.11.0 + */ +public record MappingAtomicType(String[] names, SemType[] types, SemType rest) implements AtomicType { + + public MappingAtomicType { + assert names.length == types.length; + } + + 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 = intersectCellMemberSemTypes(env, fieldPair.type1(), fieldPair.type2()); + if (isNever(cellInner(fieldPair.type1()))) { + return null; + + } + types.add(t); + } + SemType rest = intersectCellMemberSemTypes(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/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..5e9ae332e55b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingDefinition.java @@ -0,0 +1,122 @@ +/* + * 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.Builder; +import io.ballerina.runtime.api.types.semtype.Definition; +import io.ballerina.runtime.api.types.semtype.Env; +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.getUndefType; +import static io.ballerina.runtime.api.types.semtype.Core.isNever; +import static io.ballerina.runtime.api.types.semtype.Core.union; + +/** + * {@code Definition} used to create a mapping type. + * + * @since 2201.11.0 + */ +public class MappingDefinition extends Definition { + + private volatile RecAtom rec = null; + private volatile 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 = Builder.getCellContaining(env, union(rest, getUndefType()), + isNever(rest) ? CellAtomicType.CellMutability.CELL_MUT_NONE : mut); + SemType semType = define(env, cellFields, restCell); + notifyContainer(); + return semType; + } + + 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 = Builder.getCellContaining(env, field.optional ? union(type, getUndefType()) : 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..19a6938b9d68 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/Member.java @@ -0,0 +1,76 @@ +/* + * 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.getStringConst; + +/** + * Represents a member of an object type. + * + * @param name the name of the member. For methods, this is the method name, and for fields, this is the field + * name. + * @param valueTy the type of the member + * @param kind the kind of the member (either {@link Kind#Field} or {@link Kind#Method}) + * @param visibility the visibility of the member (either {@link Visibility#Public} or {@link Visibility#Private}) + * @param immutable whether the member is immutable + * @since 2201.11.0 + */ +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", getStringConst("field"), true, false); + private static final MappingDefinition.Field METHOD = + new MappingDefinition.Field("kind", getStringConst("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 = getStringConst("public"); + private static final MappingDefinition.Field PUBLIC = + new MappingDefinition.Field("visibility", PUBLIC_TAG, true, false); + private static final SemType PRIVATE_TAG = getStringConst("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/MutableSemType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MutableSemType.java new file mode 100644 index 000000000000..2059cb237766 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MutableSemType.java @@ -0,0 +1,37 @@ +/* + * 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.SemType; +import io.ballerina.runtime.internal.types.BType; + +/** + * Represents a mutable semantic type. Note in the current implementation we assume after type checking this is to be + * immutable. + * + * @since 2201.11.0 + */ +public sealed interface MutableSemType permits BType { + + SemType createSemType(); + + void resetSemType(); + + void updateInnerSemTypeIfNeeded(); +} 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..f843d04d43b2 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectDefinition.java @@ -0,0 +1,109 @@ +/* + * 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.Core; +import io.ballerina.runtime.api.types.semtype.Definition; +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.Core.createBasicSemType; +import static io.ballerina.runtime.api.types.semtype.Core.union; +import static io.ballerina.runtime.api.types.semtype.RecAtom.createDistinctRecAtom; + +/** + * {@code Definition} used to create an object type. + *

+ * Each object type is represented as mapping type (with its basic type set to object) as fallows + * {@code { "$qualifiers": { boolean isolated, "client"|"service" network }, [field_name]: { "field"|"method" kind, + * "public"|"private" visibility, VAL value; } ...{ "field" kind, "public"|"private" visibility, VAL value; } | { + * "method" kind, "public"|"private" visibility, FUNCTION value; } }} + * + * @since 2201.11.0 + */ +public class ObjectDefinition extends 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) { + 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())); + SemType semType = objectContaining(mappingType); + notifyContainer(); + return semType; + } + + 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.getReadonlyType() : Builder.getValType(), + readonly, + false), + Member.Kind.Field.field(), Member.Visibility.ALL}, Builder.getNeverType(), + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + MappingDefinition methodDefn = new MappingDefinition(); + + SemType methodMemberType = methodDefn.defineMappingTypeWrapped(env, new MappingDefinition.Field[]{ + new MappingDefinition.Field("value", Builder.getFunctionType(), true, false), + Member.Kind.Method.field(), Member.Visibility.ALL}, Builder.getNeverType(), + CellAtomicType.CellMutability.CELL_MUT_LIMITED); + return Builder.getCellContaining(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.getNeverType(), + 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..33d52a1816a9 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectQualifiers.java @@ -0,0 +1,77 @@ +/* + * 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.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.types.semtype.Builder.getBooleanConst; +import static io.ballerina.runtime.api.types.semtype.Builder.getStringConst; +import static io.ballerina.runtime.api.types.semtype.Core.union; + +/** + * Represents the qualifiers of an object type. + * + * @param isolated whether the object is isolated + * @param readonly whether the object is readonly + * @param networkQualifier the network qualifier of the object (either {@link NetworkQualifier#Client}, + * {@link NetworkQualifier#Service}, or {@link NetworkQualifier#None}) + * @since 2201.11.0 + */ +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 ? getBooleanConst(true) : Builder.getBooleanType(), + true, false); + MappingDefinition.Field networkField = networkQualifier.field(); + SemType ty = md.defineMappingTypeWrapped(env, new MappingDefinition.Field[]{isolatedField, networkField}, + Builder.getNeverType(), 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 = getStringConst("client"); + private static final MappingDefinition.Field CLIENT = + new MappingDefinition.Field("network", CLIENT_TAG, true, false); + + private static final SemType SERVICE_TAG = getStringConst("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/RegexUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/RegexUtils.java new file mode 100644 index 000000000000..39c4264ba8cc --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/RegexUtils.java @@ -0,0 +1,41 @@ +/* + * 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.Builder; +import io.ballerina.runtime.api.types.semtype.SemType; + +/** + * Utility class for creating semtypes of regex tagged basic type. + * + * @since 2201.11.0 + */ +public final class RegexUtils { + + private RegexUtils() { + + } + + public static SemType regexShape(String value) { + SemType stringSubtype = Builder.getStringConst(value); + BStringSubType stringSubType = (BStringSubType) stringSubtype.subTypeByCode(BasicTypeCode.CODE_STRING); + return Builder.basicSubType(BasicTypeCode.BT_REGEXP, stringSubType); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SemTypeHelper.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SemTypeHelper.java new file mode 100644 index 000000000000..7bad30e0127b --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SemTypeHelper.java @@ -0,0 +1,92 @@ +/* + * 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.SemType; + +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_BOOLEAN; +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_REGEXP; +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.11.0 + */ +public final class SemTypeHelper { + + private SemTypeHelper() { + } + + public static String stringRepr(SemType ty) { + return "all[" + bitSetRepr(ty.all()) + "] some [" + bitSetRepr(ty.some()) + "]"; + } + + public 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_REGEXP, "REGEXP"); + 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"); + 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/internal/types/semtype/StreamDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/StreamDefinition.java new file mode 100644 index 000000000000..2023672f5848 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/StreamDefinition.java @@ -0,0 +1,63 @@ +/* + * 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.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.Bdd; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Definition; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.types.semtype.Core.createBasicSemType; +import static io.ballerina.runtime.api.types.semtype.Core.subTypeData; + +/** + * {@code Definition} used to create a stream type. Stream is represented as a + * tuple of {@code [valueType, completionType]} + * + * @since 2201.11.0 + */ +public class StreamDefinition extends Definition { + + private final ListDefinition listDefinition = new ListDefinition(); + + @Override + public SemType getSemType(Env env) { + return streamContaining(listDefinition.getSemType(env)); + } + + public SemType define(Env env, SemType valueType, SemType completionType) { + if (Builder.getValType() == completionType && Builder.getValType() == valueType) { + return Builder.getStreamType(); + } + SemType tuple = listDefinition.defineListTypeWrapped(env, new SemType[]{valueType, completionType}, 2, + Builder.getNeverType(), CellAtomicType.CellMutability.CELL_MUT_LIMITED); + SemType semType = streamContaining(tuple); + notifyContainer(); + return semType; + } + + private SemType streamContaining(SemType tupleType) { + SubTypeData bdd = subTypeData(tupleType, BasicTypeCode.BT_LIST); + assert bdd instanceof Bdd; + return createBasicSemType(BasicTypeCode.BT_STREAM, (Bdd) bdd); + } +} 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..a418583d10af --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubTypeData.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.types.semtype; + +/** + * Marker interface for SubTypeData. + * + * @since 2201.11.0 + */ +public interface SubTypeData { + +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePair.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePair.java new file mode 100644 index 000000000000..d56e4bd33899 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/SubtypePair.java @@ -0,0 +1,40 @@ +/* + * 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; + +/** + * Represents the corresponding subtype pairs of two semtypes. + * + * @param typeCode the type code of the semtype + * @param subType1 the first subtype. This will if the first semtype don't have + * this subtype + * @param subType2 the second subtype. This will if the second semtype don't + * have this subtype + * @since 2201.11.0 + */ +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..f061b012896a --- /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.11.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_UNDEF + 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)); + 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..95844611c535 --- /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.11.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/types/semtype/TableUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/TableUtils.java new file mode 100644 index 000000000000..158259252366 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/TableUtils.java @@ -0,0 +1,125 @@ +/* + * 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.BasicTypeCode; +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.Core; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +import java.util.Optional; + +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_LIMITED; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_UNLIMITED; + +/** + * Utility class for creating semtypes of table type. + * + * @since 2201.11.0 + */ +public final class TableUtils { + + private static final SemType[] EMPTY_SEMTYPE_ARR = new SemType[0]; + + private TableUtils() { + } + + public static SemType acceptedTypeContainingKeySpecifier(Context cx, SemType tableConstraint, String[] fieldNames) { + return tableContainingKeySpecifierInner(fieldNames, cx, tableConstraint, CELL_MUT_UNLIMITED); + } + + public static SemType tableContainingKeySpecifier(Context cx, SemType tableConstraint, String[] fieldNames) { + return tableContainingKeySpecifierInner(fieldNames, cx, tableConstraint, CELL_MUT_LIMITED); + } + + private static SemType tableContainingKeySpecifierInner(String[] fieldNames, Context cx, SemType tableConstraint, + CellAtomicType.CellMutability cellMutLimited) { + SemType[] fieldNameSingletons = new SemType[fieldNames.length]; + SemType[] fieldTypes = new SemType[fieldNames.length]; + for (int i = 0; i < fieldNames.length; i++) { + SemType key = Builder.getStringConst(fieldNames[i]); + fieldNameSingletons[i] = key; + fieldTypes[i] = Core.mappingMemberTypeInnerVal(cx, tableConstraint, key); + } + + SemType normalizedKs = + new ListDefinition().defineListTypeWrapped(cx.env, fieldNameSingletons, fieldNameSingletons.length, + Builder.getNeverType(), CELL_MUT_NONE); + + SemType normalizedKc = fieldNames.length > 1 ? new ListDefinition().defineListTypeWrapped(cx.env, fieldTypes, + fieldTypes.length, Builder.getNeverType(), CELL_MUT_NONE) : fieldTypes[0]; + + return tableContaining(cx.env, tableConstraint, normalizedKc, normalizedKs, cellMutLimited); + } + + public static SemType acceptedTypeContainingKeyConstraint(Context cx, SemType tableConstraint, + SemType keyConstraint) { + return tableContainingKeyConstraintInner(cx, tableConstraint, keyConstraint, CELL_MUT_UNLIMITED); + } + + public static SemType tableContainingKeyConstraint(Context cx, SemType tableConstraint, SemType keyConstraint) { + return tableContainingKeyConstraintInner(cx, tableConstraint, keyConstraint, CELL_MUT_LIMITED); + } + + private static SemType tableContainingKeyConstraintInner(Context cx, SemType tableConstraint, SemType keyConstraint, + CellAtomicType.CellMutability mut) { + Optional lat = Core.listAtomicType(cx, keyConstraint); + SemType normalizedKc = lat.map(atom -> { + FixedLengthArray member = atom.members(); + return switch (member.fixedLength()) { + case 0 -> Builder.getValType(); + case 1 -> Core.cellAtomicType(member.initial()[0]).orElseThrow().ty(); + default -> keyConstraint; + }; + }).orElse(keyConstraint); + return tableContaining(cx.env, tableConstraint, normalizedKc, Builder.getValType(), mut); + } + + public static SemType tableContaining(Env env, SemType tableConstraint) { + return tableContaining(env, tableConstraint, CELL_MUT_LIMITED); + } + + public static SemType acceptedType(Env env, SemType tableConstraint) { + return tableContaining(env, tableConstraint, CELL_MUT_UNLIMITED); + } + + private static SemType tableContaining(Env env, SemType tableConstraint, CellAtomicType.CellMutability mut) { + return tableContaining(env, tableConstraint, Builder.getValType(), Builder.getValType(), mut); + } + + private static SemType tableContaining(Env env, SemType tableConstraint, SemType normalizedKc, SemType normalizedKs, + CellAtomicType.CellMutability mut) { + tableConstraint = Core.intersect(tableConstraint, Builder.getMappingType()); + ListDefinition typeParamArrDef = new ListDefinition(); + SemType typeParamArray = typeParamArrDef.defineListTypeWrapped(env, EMPTY_SEMTYPE_ARR, 0, tableConstraint, mut); + + ListDefinition listDef = new ListDefinition(); + SemType tupleType = + listDef.defineListTypeWrapped(env, new SemType[]{typeParamArray, normalizedKc, normalizedKs}, 3, + Builder.getNeverType(), + CELL_MUT_LIMITED); + Bdd bdd = (Bdd) Core.subTypeData(tupleType, BasicTypeCode.BT_LIST); + return Core.createBasicSemType(BasicTypeCode.BT_TABLE, bdd); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/TypedescUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/TypedescUtils.java new file mode 100644 index 000000000000..4bf7b6a78406 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/TypedescUtils.java @@ -0,0 +1,54 @@ +/* + * 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.BasicTypeCode; +import io.ballerina.runtime.api.types.semtype.Bdd; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; + +import static io.ballerina.runtime.api.types.semtype.Core.createBasicSemType; +import static io.ballerina.runtime.api.types.semtype.Core.subTypeData; + +/** + * Utility class for creating semtypes of typedesc type. + * + * @since 2201.11.0 + */ +public final class TypedescUtils { + + private static final MappingDefinition.Field[] EMPTY_FIELDS = new MappingDefinition.Field[0]; + + private TypedescUtils() { + + } + + public static SemType typedescContaining(Env env, SemType constraint) { + if (constraint == Builder.getValType()) { + return Builder.getTypeDescType(); + } + MappingDefinition md = new MappingDefinition(); + SemType mappingType = md.defineMappingTypeWrapped(env, EMPTY_FIELDS, constraint, + CellAtomicType.CellMutability.CELL_MUT_NONE); + Bdd bdd = (Bdd) subTypeData(mappingType, BasicTypeCode.BT_MAPPING); + return createBasicSemType(BasicTypeCode.BT_TYPEDESC, bdd); + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/XmlUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/XmlUtils.java new file mode 100644 index 000000000000..f05813ead001 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/XmlUtils.java @@ -0,0 +1,145 @@ +/* + * 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.BddAllOrNothing; +import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Core; +import io.ballerina.runtime.api.types.semtype.RecAtom; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.SubType; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; + +/** + * Utility class for creating semtypes of XML type. + * + * @since 2201.11.0 + */ +public final class XmlUtils { + + public static final int XML_PRIMITIVE_NEVER = 1; + public static final int XML_PRIMITIVE_TEXT = 1 << 1; + public static final int XML_PRIMITIVE_ELEMENT_RO = 1 << 2; + public static final int XML_PRIMITIVE_PI_RO = 1 << 3; + public static final int XML_PRIMITIVE_COMMENT_RO = 1 << 4; + public static final int XML_PRIMITIVE_ELEMENT_RW = 1 << 5; + public static final int XML_PRIMITIVE_PI_RW = 1 << 6; + public static final int XML_PRIMITIVE_COMMENT_RW = 1 << 7; + + public static final int XML_PRIMITIVE_RO_SINGLETON = XML_PRIMITIVE_TEXT | XML_PRIMITIVE_ELEMENT_RO + | XML_PRIMITIVE_PI_RO | XML_PRIMITIVE_COMMENT_RO; + public static final int XML_PRIMITIVE_RO_MASK = XML_PRIMITIVE_NEVER | XML_PRIMITIVE_RO_SINGLETON; + public static final int XML_PRIMITIVE_RW_MASK = XML_PRIMITIVE_ELEMENT_RW | XML_PRIMITIVE_PI_RW + | XML_PRIMITIVE_COMMENT_RW; + public static final int XML_PRIMITIVE_SINGLETON = XML_PRIMITIVE_RO_SINGLETON | XML_PRIMITIVE_RW_MASK; + public static final int XML_PRIMITIVE_ALL_MASK = XML_PRIMITIVE_RO_MASK | XML_PRIMITIVE_RW_MASK; + + public static final SubTypeData XML_SUBTYPE_TOP = from(XML_PRIMITIVE_ALL_MASK, BddAllOrNothing.ALL); + public static final SubType XML_SUBTYPE_RO = + BXmlSubType.createDelegate(XML_PRIMITIVE_RO_MASK, + bddAtom(RecAtom.createRecAtom(XML_PRIMITIVE_RO_SINGLETON))); + + private XmlUtils() { + } + + public static SemType xmlSingleton(int primitive) { + if (XmlSingletonCache.isCached(primitive)) { + return XmlSingletonCache.get(primitive); + } + return createXmlSingleton(primitive); + } + + private static SemType createXmlSingleton(int primitive) { + return createXmlSemtype(createXmlSubtype(primitive, BddAllOrNothing.NOTHING)); + } + + private static SemType createXmlSemtype(SubTypeData xmlSubtype) { + if (xmlSubtype instanceof AllOrNothing) { + return xmlSubtype == AllOrNothing.ALL ? Builder.getXmlType() : Builder.getNeverType(); + } + assert xmlSubtype instanceof BXmlSubType : "subtype must be wrapped by delegate by now"; + return Builder.basicSubType(BasicTypeCode.BT_XML, (SubType) xmlSubtype); + } + + private static SubTypeData createXmlSubtype(int primitives, Bdd sequence) { + int p = primitives & XML_PRIMITIVE_ALL_MASK; + if (primitiveShouldIncludeNever(p)) { + p |= XML_PRIMITIVE_NEVER; + } + if (sequence == BddAllOrNothing.ALL && p == XML_PRIMITIVE_ALL_MASK) { + return AllOrNothing.ALL; + } else if (sequence == BddAllOrNothing.NOTHING && p == 0) { + return AllOrNothing.NOTHING; + } + return from(p, sequence); + } + + private static boolean primitiveShouldIncludeNever(int primitive) { + return (primitive & XML_PRIMITIVE_TEXT) == XML_PRIMITIVE_TEXT; + } + + public static SubTypeData from(int primitives, Bdd sequence) { + return BXmlSubType.createDelegate(primitives, sequence); + } + + public static SemType xmlSequence(SemType constituentType) { + assert Core.isSubtypeSimple(constituentType, Builder.getXmlType()) : + "It is a precondition that constituentType is a subtype of XML"; + if (Core.isNever(constituentType)) { + return xmlSequence(xmlSingleton(XML_PRIMITIVE_NEVER)); + } else if (constituentType.some() == 0) { + assert Core.isNever(Core.diff(Builder.getXmlType(), constituentType)); + return constituentType; + } else { + SubType xmlSubType = + Core.getComplexSubtypeData(constituentType, BasicTypeCode.BT_XML); + if (!xmlSubType.isAll() && !xmlSubType.isNothing()) { + xmlSubType = makeXmlSequence((BXmlSubType) xmlSubType); + } + return createXmlSemtype((SubTypeData) xmlSubType); + } + } + + private static SubType makeXmlSequence(BXmlSubType xmlSubType) { + int primitives = xmlSubType.primitives() | XML_PRIMITIVE_NEVER; + int atom = xmlSubType.primitives() & XML_PRIMITIVE_SINGLETON; + Bdd sequence = (Bdd) xmlSubType.bdd().union(bddAtom(RecAtom.createRecAtom(atom))); + return BXmlSubType.createDelegate(primitives, sequence); + } + + private static final class XmlSingletonCache { + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private static boolean isCached(int primitive) { + return Integer.bitCount(primitive) < 3; + } + + private static SemType get(int primitive) { + return CACHE.computeIfAbsent(primitive, XmlUtils::createXmlSingleton); + } + + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/MapUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/MapUtils.java index 2ae27c4cd2cb..62ac70673626 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/MapUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/MapUtils.java @@ -20,22 +20,22 @@ 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.MapType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +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.utils.TypeUtils; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.errors.ErrorCodes; import io.ballerina.runtime.internal.errors.ErrorHelper; -import io.ballerina.runtime.internal.types.BMapType; import io.ballerina.runtime.internal.types.BRecordType; import io.ballerina.runtime.internal.types.BTypeReferenceType; -import io.ballerina.runtime.internal.types.BUnionType; import io.ballerina.runtime.internal.values.MapValue; -import java.util.List; - import static io.ballerina.runtime.api.constants.RuntimeConstants.MAP_LANG_LIB; import static io.ballerina.runtime.internal.errors.ErrorReasons.INHERENT_TYPE_VIOLATION_ERROR_IDENTIFIER; import static io.ballerina.runtime.internal.errors.ErrorReasons.MAP_KEY_NOT_FOUND_ERROR; @@ -56,7 +56,7 @@ public static void handleMapStore(MapValue mapValue, BString fi updateMapValue(TypeUtils.getImpliedType(mapValue.getType()), mapValue, fieldName, value); } - public static void handleInherentTypeViolatingMapUpdate(Object value, BMapType mapType) { + public static void handleInherentTypeViolatingMapUpdate(Object value, MapType mapType) { if (TypeChecker.checkIsType(value, mapType.getConstrainedType())) { return; } @@ -118,17 +118,7 @@ public static boolean handleInherentTypeViolatingRecordUpdate( } private static boolean containsNilType(Type type) { - type = TypeUtils.getImpliedType(type); - int tag = type.getTag(); - if (tag == TypeTags.UNION_TAG) { - List memTypes = ((BUnionType) type).getMemberTypes(); - for (Type memType : memTypes) { - if (containsNilType(memType)) { - return true; - } - } - } - return tag == TypeTags.NULL_TAG; + return Core.containsBasicType(SemType.tryInto(type), Builder.getNilType()); } public static BError createOpNotSupportedError(Type type, String op) { @@ -152,7 +142,7 @@ private static void updateMapValue(Type mapType, MapValue mapVa switch (mapType.getTag()) { case TypeTags.MAP_TAG: - handleInherentTypeViolatingMapUpdate(value, (BMapType) mapType); + handleInherentTypeViolatingMapUpdate(value, (MapType) mapType); mapValue.put(fieldName, value); return; case TypeTags.RECORD_TYPE_TAG: diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/ValueConverter.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/ValueConverter.java index ec772ed9b133..b610c8a18316 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/ValueConverter.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/ValueConverter.java @@ -31,6 +31,10 @@ import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.TypedescType; +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.api.values.BArray; @@ -57,6 +61,7 @@ import io.ballerina.runtime.internal.values.ReadOnlyUtils; import io.ballerina.runtime.internal.values.TableValueImpl; import io.ballerina.runtime.internal.values.TupleValueImpl; +import io.ballerina.runtime.internal.values.XmlSequence; import java.util.ArrayList; import java.util.HashMap; @@ -146,6 +151,7 @@ private static Object convert(Object value, Type targetType, Set if (matchingType.isReadOnly()) { newValue = CloneUtils.cloneReadOnly(newValue); } + newValue = xmlSequenceHack(newValue, matchingType); break; } @@ -171,6 +177,27 @@ private static Object convert(Object value, Type targetType, Set return newValue; } + // This is a hack to workaround #43231 + private static Object xmlSequenceHack(Object value, Type targetType) { + if (!(value instanceof XmlSequence xmlSequence)) { + return value; + } + Context cx = TypeChecker.context(); + List list = new ArrayList<>(); + SemType targetSemType = SemType.tryInto(targetType); + for (BXml child : xmlSequence.getChildrenList()) { + SemType childType = SemType.tryInto(child.getType()); + boolean isReadonly = + Core.isSubType(cx, Core.intersect(childType, targetSemType), Builder.getReadonlyType()); + if (isReadonly) { + list.add((BXml) CloneUtils.cloneReadOnly(child)); + } else { + list.add(child); + } + } + return new XmlSequence(list); + } + private static Type getTargetFromTypeDesc(Type targetType) { Type referredType = TypeUtils.getImpliedType(targetType); if (referredType.getTag() == TypeTags.TYPEDESC_TAG) { 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 4768e51ecd52..a129148baf23 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,12 +20,17 @@ import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.internal.errors.ErrorHelper; import io.ballerina.runtime.internal.json.JsonGenerator; import io.ballerina.runtime.internal.types.BTupleType; import io.ballerina.runtime.internal.types.BUnionType; +import io.ballerina.runtime.internal.types.TypeWithShape; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; import io.ballerina.runtime.internal.utils.IteratorUtils; import java.io.ByteArrayOutputStream; @@ -33,6 +38,7 @@ import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import static io.ballerina.runtime.api.constants.RuntimeConstants.ARRAY_LANG_LIB; @@ -51,9 +57,10 @@ * * @since 1.1.0 */ -public abstract class AbstractArrayValue implements ArrayValue { +public abstract class AbstractArrayValue implements ArrayValue, RecursiveValue { static final int SYSTEM_ARRAY_MAX = Integer.MAX_VALUE - 8; + private final ThreadLocal readonlyAttachedDefinition = new ThreadLocal<>(); /** * The maximum size of arrays to allocate. @@ -77,6 +84,9 @@ public void append(Object value) { @Override public boolean equals(Object o, Set visitedValues) { + if (!(o instanceof ArrayValue arrayValue)) { + return false; + } ValuePair compValuePair = new ValuePair(this, o); for (ValuePair valuePair : visitedValues) { if (valuePair.equals(compValuePair)) { @@ -85,7 +95,6 @@ public boolean equals(Object o, Set visitedValues) { } visitedValues.add(compValuePair); - ArrayValue arrayValue = (ArrayValue) o; if (arrayValue.size() != this.size()) { return false; } @@ -303,4 +312,25 @@ public boolean hasNext() { return cursor < length; } } + + @Override + public synchronized ListDefinition getReadonlyShapeDefinition() { + return readonlyAttachedDefinition.get(); + } + + @Override + public synchronized void setReadonlyShapeDefinition(ListDefinition definition) { + readonlyAttachedDefinition.set(definition); + } + + @Override + public synchronized void resetReadonlyShapeDefinition() { + readonlyAttachedDefinition.remove(); + } + + @Override + public Optional inherentTypeOf(Context cx) { + TypeWithShape typeWithShape = (TypeWithShape) getType(); + return typeWithShape.inherentTypeOf(cx, ShapeAnalyzer::inherentTypeOf, this); + } } 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 07d20399f30d..5457ca79574e 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,9 @@ 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.Context; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; @@ -35,10 +38,13 @@ import io.ballerina.runtime.internal.errors.ErrorCodes; import io.ballerina.runtime.internal.errors.ErrorHelper; import io.ballerina.runtime.internal.types.BObjectType; +import io.ballerina.runtime.internal.types.TypeWithShape; +import io.ballerina.runtime.internal.types.semtype.ObjectDefinition; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.StringJoiner; import static io.ballerina.runtime.api.constants.RuntimeConstants.DOT; @@ -57,10 +63,12 @@ * * @since 0.995.0 */ -public abstract class AbstractObjectValue implements ObjectValue { +public abstract class AbstractObjectValue implements ObjectValue, RecursiveValue { private BTypedesc typedesc; private final BObjectType objectType; private final Type type; + private SemType shape; + private final ThreadLocal readonlyAttachedDefinition = new ThreadLocal<>(); private final HashMap nativeData = new HashMap<>(); @@ -233,4 +241,33 @@ 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; + } + + @Override + public Optional inherentTypeOf(Context cx) { + TypeWithShape typeWithShape = (TypeWithShape) getType(); + return typeWithShape.inherentTypeOf(cx, ShapeAnalyzer::inherentTypeOf, this); + } + + @Override + public ObjectDefinition getReadonlyShapeDefinition() { + return readonlyAttachedDefinition.get(); + } + + @Override + public void setReadonlyShapeDefinition(ObjectDefinition definition) { + readonlyAttachedDefinition.set(definition); + } + + @Override + public void resetReadonlyShapeDefinition() { + readonlyAttachedDefinition.remove(); + } } 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 ad1896b75e76..6413e10fd648 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.PredefinedTypes; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +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; } @@ -1257,12 +1260,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); } @@ -1406,4 +1409,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 ec48ef7789af..d5e5e918e02e 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 @@ -22,17 +22,22 @@ import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.types.PredefinedTypes; 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.BDecimal; import io.ballerina.runtime.api.values.BLink; 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 io.ballerina.runtime.internal.utils.ErrorUtils; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.Map; +import java.util.Optional; /** *

@@ -60,8 +65,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; @@ -83,7 +90,7 @@ public DecimalValue(String value) { throw exception; } this.value = getValidDecimalValue(bd); - + this.singletonType = BDecimalType.singletonType(this.value); if (!this.booleanValue()) { this.valueKind = DecimalValueKind.ZERO; } @@ -229,7 +236,7 @@ public BigDecimal value() { */ @Override public Type getType() { - return PredefinedTypes.TYPE_DECIMAL; + return singletonType; } //========================= Mathematical operations supported =============================== @@ -480,4 +487,9 @@ public static DecimalValue valueOfJ(BigDecimal value) { return new DecimalValue(new BigDecimal(value.toString(), MathContext.DECIMAL128) .setScale(1, BigDecimal.ROUND_HALF_EVEN)); } + + @Override + public Optional inherentTypeOf(Context cx) { + return Optional.of(Builder.getDecimalConst(value)); + } } 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 f8cd391a127a..140cd7d0dc08 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 @@ -86,7 +86,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)); } @@ -469,7 +469,9 @@ private boolean isCompilerAddedName(String name) { */ @Override public boolean equals(Object o, Set visitedValues) { - ErrorValue errorValue = (ErrorValue) o; + if (!(o instanceof ErrorValue errorValue)) { + return false; + } return isEqual(this.getMessage(), errorValue.getMessage(), visitedValues) && ((MapValueImpl) this.getDetails()).equals(errorValue.getDetails(), visitedValues) && isEqual(this.getCause(), errorValue.getCause(), visitedValues); 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 35dd9a28be60..6e116666a45b 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,12 +20,15 @@ import io.ballerina.runtime.api.Runtime; import io.ballerina.runtime.api.constants.RuntimeConstants; 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.api.values.BFunctionPointer; import io.ballerina.runtime.api.values.BLink; import io.ballerina.runtime.api.values.BTypedesc; import io.ballerina.runtime.internal.BalRuntime; import java.util.Map; +import java.util.Optional; import java.util.function.Function; /** @@ -101,4 +104,9 @@ public String getName() { public String toString() { return RuntimeConstants.EMPTY; } + + @Override + public Optional inherentTypeOf(Context cx) { + return Optional.of(SemType.tryInto(getType())); + } } 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 1361db3b3697..bbd80289daed 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 @@ -19,9 +19,13 @@ import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; @@ -41,10 +45,11 @@ import io.ballerina.runtime.internal.json.JsonInternalUtils; import io.ballerina.runtime.internal.scheduling.Scheduler; import io.ballerina.runtime.internal.types.BField; -import io.ballerina.runtime.internal.types.BMapType; import io.ballerina.runtime.internal.types.BRecordType; import io.ballerina.runtime.internal.types.BTupleType; import io.ballerina.runtime.internal.types.BUnionType; +import io.ballerina.runtime.internal.types.TypeWithShape; +import io.ballerina.runtime.internal.types.semtype.MappingDefinition; import io.ballerina.runtime.internal.utils.CycleUtils; import io.ballerina.runtime.internal.utils.IteratorUtils; import io.ballerina.runtime.internal.utils.MapUtils; @@ -60,6 +65,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import java.util.stream.Collectors; @@ -93,13 +99,15 @@ * @since 0.995.0 */ public class MapValueImpl extends LinkedHashMap implements RefValue, CollectionValue, MapValue, - BMap { + BMap, RecursiveValue { private BTypedesc typedesc; private Type type; private Type referredType; private final Map nativeData = new HashMap<>(); private Type iteratorNextReturnType; + private SemType shape; + private final ThreadLocal readonlyAttachedDefinition = new ThreadLocal<>(); public MapValueImpl(TypedescValue typedesc) { this(typedesc.getDescribingType()); @@ -233,7 +241,7 @@ public V fillAndGet(Object key) { expectedType = recordType.restFieldType; } } else { - expectedType = ((BMapType) this.referredType).getConstrainedType(); + expectedType = ((MapType) this.referredType).getConstrainedType(); } if (!TypeChecker.hasFillerValue(expectedType)) { @@ -347,7 +355,7 @@ protected void populateInitialValues(BMapInitialValueEntry[] initialValues) { @Override public void populateInitialValue(K key, V value) { if (referredType.getTag() == TypeTags.MAP_TAG) { - MapUtils.handleInherentTypeViolatingMapUpdate(value, (BMapType) referredType); + MapUtils.handleInherentTypeViolatingMapUpdate(value, (MapType) referredType); putValue(key, value); } else { BString fieldName = (BString) key; @@ -609,6 +617,21 @@ public IteratorValue getIterator() { return new MapIterator<>(new LinkedHashSet<>(this.entrySet()).iterator()); } + @Override + public synchronized MappingDefinition getReadonlyShapeDefinition() { + return readonlyAttachedDefinition.get(); + } + + @Override + public synchronized void setReadonlyShapeDefinition(MappingDefinition definition) { + readonlyAttachedDefinition.set(definition); + } + + @Override + public synchronized void resetReadonlyShapeDefinition() { + readonlyAttachedDefinition.remove(); + } + /** * {@link MapIterator} iteration provider for ballerina maps. * @@ -688,7 +711,7 @@ public Map getNativeDataMap() { private void initializeIteratorNextReturnType() { Type type; if (this.referredType.getTag() == PredefinedTypes.TYPE_MAP.getTag()) { - BMapType mapType = (BMapType) this.referredType; + MapType mapType = (MapType) this.referredType; type = mapType.getConstrainedType(); } else { BRecordType recordType = (BRecordType) this.referredType; @@ -722,4 +745,20 @@ 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; + } + + @Override + public Optional inherentTypeOf(Context cx) { + TypeWithShape typeWithShape = (TypeWithShape) type; + return typeWithShape.inherentTypeOf(cx, ShapeAnalyzer::inherentTypeOf, this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ReadOnlyUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ReadOnlyUtils.java index c9ba580378da..8e23ef16c232 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ReadOnlyUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ReadOnlyUtils.java @@ -26,6 +26,7 @@ import io.ballerina.runtime.api.types.Field; import io.ballerina.runtime.api.types.IntersectableReferenceType; import io.ballerina.runtime.api.types.IntersectionType; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.ReferenceType; import io.ballerina.runtime.api.types.SelectivelyImmutableReferenceType; @@ -184,9 +185,9 @@ private static BIntersectionType setImmutableIntersectionType(Type type, Set Type of the definition + * + * @since 2201.11.0 + */ +interface RecursiveValue { + + E getReadonlyShapeDefinition(); + + void setReadonlyShapeDefinition(E definition); + + void resetReadonlyShapeDefinition(); +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java index c9a4fd8f30c7..a00800f64b53 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java @@ -17,12 +17,17 @@ import io.ballerina.runtime.api.types.PredefinedTypes; 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.BLink; import io.ballerina.runtime.api.values.BRegexpValue; import io.ballerina.runtime.api.values.BTypedesc; +import io.ballerina.runtime.internal.types.semtype.RegexUtils; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import static io.ballerina.runtime.internal.utils.ValueUtils.getTypedescValue; @@ -41,9 +46,11 @@ public class RegExpValue implements BRegexpValue, RefValue { private final RegExpDisjunction regExpDisjunction; private BTypedesc typedesc; private static final Type type = PredefinedTypes.TYPE_READONLY_ANYDATA; + private final SemType shape; public RegExpValue(RegExpDisjunction regExpDisjunction) { this.regExpDisjunction = regExpDisjunction; + this.shape = RegexUtils.regexShape(regExpDisjunction.stringValue(null)); } @Override @@ -76,11 +83,6 @@ public int hashCode() { return Objects.hash(this.regExpDisjunction); } - @Override - public boolean equals(Object obj) { - return this == obj; - } - @Override public BTypedesc getTypedesc() { if (this.typedesc == null) { @@ -123,4 +125,19 @@ public boolean equals(Object o, Set visitedValues) { } return this.stringValue(null).equals(rhsRegExpValue.stringValue(null)); } + + @Override + public SemType widenedType() { + return Builder.getRegexType(); + } + + @Override + public Optional shapeOf() { + return Optional.of(this.shape); + } + + @Override + public Optional inherentTypeOf(Context cx) { + return shapeOf(); + } } 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 3898b3aa8ca4..d553b300f4a8 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,12 +17,16 @@ */ package io.ballerina.runtime.internal.values; -import io.ballerina.runtime.api.types.PredefinedTypes; 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.BLink; import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.internal.types.BStringType; import java.util.Map; +import java.util.Optional; /** * Class representing ballerina strings. @@ -33,15 +37,19 @@ public abstract class StringValue implements BString, SimpleValue { final String value; final boolean isNonBmp; + private final Type type; + private final SemType shape; protected StringValue(String value, boolean isNonBmp) { this.value = value; this.isNonBmp = isNonBmp; + this.type = BStringType.singletonType(value); + this.shape = Builder.getStringConst(value); } @Override public Type getType() { - return PredefinedTypes.TYPE_STRING; + return type; } @Override @@ -100,4 +108,8 @@ public boolean equals(Object str) { return false; } + @Override + public Optional inherentTypeOf(Context cx) { + return Optional.of(shape); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TableValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TableValueImpl.java index c027da267680..5ce57d89f406 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TableValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TableValueImpl.java @@ -20,9 +20,13 @@ import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.TableType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; @@ -37,12 +41,12 @@ import io.ballerina.runtime.internal.errors.ErrorCodes; import io.ballerina.runtime.internal.errors.ErrorHelper; import io.ballerina.runtime.internal.types.BIntersectionType; -import io.ballerina.runtime.internal.types.BMapType; import io.ballerina.runtime.internal.types.BRecordType; import io.ballerina.runtime.internal.types.BTableType; import io.ballerina.runtime.internal.types.BTupleType; import io.ballerina.runtime.internal.types.BTypeReferenceType; import io.ballerina.runtime.internal.types.BUnionType; +import io.ballerina.runtime.internal.types.TypeWithShape; import io.ballerina.runtime.internal.utils.CycleUtils; import io.ballerina.runtime.internal.utils.IteratorUtils; import io.ballerina.runtime.internal.utils.TableUtils; @@ -58,6 +62,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import java.util.TreeMap; @@ -430,7 +435,7 @@ private Type getTableConstraintField(Type constraintType, String fieldName) { Map fieldList = ((BRecordType) constraintType).getFields(); return fieldList.get(fieldName).getFieldType(); case TypeTags.MAP_TAG: - return ((BMapType) constraintType).getConstrainedType(); + return ((MapType) constraintType).getConstrainedType(); case TypeTags.INTERSECTION_TAG: Type effectiveType = ((BIntersectionType) constraintType).getEffectiveType(); return getTableConstraintField(effectiveType, fieldName); @@ -788,7 +793,7 @@ public MultiKeyWrapper() { Arrays.stream(fieldNames) .forEach(field -> keyTypes.add(recordType.getFields().get(field).getFieldType())); } else if (constraintType.getTag() == TypeTags.MAP_TAG) { - BMapType mapType = (BMapType) constraintType; + MapType mapType = (MapType) constraintType; Arrays.stream(fieldNames).forEach(field -> keyTypes.add(mapType.getConstrainedType())); } keyType = new BTupleType(keyTypes); @@ -904,4 +909,10 @@ public BObject getObjectValue(BString key) { public BArray getArrayValue(BString key) { return (BArray) get(key); } + + @Override + public Optional inherentTypeOf(Context cx) { + TypeWithShape typeWithShape = (TypeWithShape) type; + return typeWithShape.inherentTypeOf(cx, ShapeAnalyzer::inherentTypeOf, this); + } } 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 b6db397f4617..6086c28633ad 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.types.TupleType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +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; @@ -75,6 +76,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) { @@ -871,4 +873,14 @@ private void validateInherentTypeOfExistingMembers(int index, int offset) { } } } + + @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/XmlValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlValue.java index 30c606d8c4b2..ea862ca298db 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlValue.java @@ -20,6 +20,9 @@ import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.XmlNodeType; +import io.ballerina.runtime.api.types.semtype.Context; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BLink; import io.ballerina.runtime.api.values.BMap; @@ -27,12 +30,14 @@ import io.ballerina.runtime.api.values.BTypedesc; import io.ballerina.runtime.api.values.BXml; import io.ballerina.runtime.api.values.BXmlQName; +import io.ballerina.runtime.internal.types.TypeWithShape; import io.ballerina.runtime.internal.utils.IteratorUtils; import io.ballerina.runtime.internal.xml.BallerinaXmlSerializer; import java.io.OutputStream; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.xml.namespace.QName; @@ -75,30 +80,32 @@ public BString getAttribute(BXmlQName attributeName) { } /** - * Set the value of a single attribute. If the attribute already exsists, then the value will be updated. + * Set the value of a single attribute. If the attribute already exsists, then + * the value will be updated. * Otherwise a new attribute will be added. * * @param attributeName Qualified name of the attribute - * @param value Value of the attribute + * @param value Value of the attribute */ @Override @Deprecated public void setAttribute(BXmlQName attributeName, String value) { setAttributeOnInitialization(attributeName.getLocalName(), attributeName.getUri(), attributeName.getPrefix(), - value); + value); } /** - * Set the value of a single attribute. If the attribute already exsists, then the value will be updated. + * Set the value of a single attribute. If the attribute already exsists, then + * the value will be updated. * Otherwise a new attribute will be added. * * @param attributeName Qualified name of the attribute - * @param value Value of the attribute + * @param value Value of the attribute */ @Deprecated public void setAttribute(BXmlQName attributeName, BString value) { setAttributeOnInitialization(attributeName.getLocalName(), attributeName.getUri(), attributeName.getPrefix(), - value.getValue()); + value.getValue()); } /** @@ -147,12 +154,13 @@ public Type getType() { protected abstract void setAttributesOnInitialization(BMap attributes); protected abstract void setAttributeOnInitialization(String localName, String namespace, String prefix, - String value); + String value); // private methods protected static void handleXmlException(String message, Throwable t) { - // Here local message of the cause is logged whenever possible, to avoid java class being logged + // Here local message of the cause is logged whenever possible, to avoid java + // class being logged // along with the error message. if (t.getCause() != null) { throw ErrorCreator.createError(StringUtils.fromString(message + t.getCause().getMessage())); @@ -184,10 +192,12 @@ protected QName getQname(String qname) { } /** - * Recursively traverse and add the descendant with the given name to the descendants list. - * @param descendants List to add descendants + * Recursively traverse and add the descendant with the given name to the + * descendants list. + * + * @param descendants List to add descendants * @param currentElement Current node - * @param qnames Qualified names of the descendants to search + * @param qnames Qualified names of the descendants to search */ protected void addDescendants(List descendants, XmlItem currentElement, List qnames) { for (BXml child : currentElement.getChildrenSeq().getChildrenList()) { @@ -273,4 +283,9 @@ public Type getIteratorNextReturnType() { return iteratorNextReturnType; } + @Override + public Optional inherentTypeOf(Context cx) { + TypeWithShape typeWithShape = (TypeWithShape) type; + return typeWithShape.inherentTypeOf(cx, ShapeAnalyzer::inherentTypeOf, this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/module-info.java b/bvm/ballerina-runtime/src/main/java/module-info.java index d09fee04a70a..fbb04210f3f4 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; @@ -62,4 +63,5 @@ exports io.ballerina.runtime.internal to ballerina.debug.adapter.core, io.ballerina.cli, io.ballerina.cli.utils, io.ballerina.java, io.ballerina.lang, io.ballerina.lang.array, io.ballerina.lang.bool, io.ballerina.lang.decimal, io.ballerina.lang.error, io.ballerina.lang.floatingpoint, io.ballerina.lang.function, io.ballerina.lang.integer, io.ballerina.lang.internal, io.ballerina.lang.map, io.ballerina.lang.regexp, io.ballerina.lang.table, io.ballerina.lang.test, io.ballerina.lang.transaction, io.ballerina.lang.value, io.ballerina.lang.xml, io.ballerina.log.api, io.ballerina.runtime.profiler, io.ballerina.shell, io.ballerina.testerina.core, io.ballerina.testerina.runtime, org.ballerinalang.debugadapter.runtime; exports io.ballerina.runtime.api.repository; exports io.ballerina.runtime.internal.repository to ballerina.debug.adapter.core, io.ballerina.cli, io.ballerina.cli.utils, io.ballerina.java, io.ballerina.lang, io.ballerina.lang.array, io.ballerina.lang.bool, io.ballerina.lang.decimal, io.ballerina.lang.error, io.ballerina.lang.floatingpoint, io.ballerina.lang.function, io.ballerina.lang.integer, io.ballerina.lang.internal, io.ballerina.lang.map, io.ballerina.lang.regexp, io.ballerina.lang.table, io.ballerina.lang.test, io.ballerina.lang.transaction, io.ballerina.lang.value, io.ballerina.lang.xml, io.ballerina.log.api, io.ballerina.runtime.profiler, io.ballerina.shell, io.ballerina.testerina.core, io.ballerina.testerina.runtime, org.ballerinalang.debugadapter.runtime; + exports io.ballerina.runtime.internal.types.semtype to io.ballerina.runtime.api.types.semtype, io.ballerina.runtime.internal.types; } diff --git a/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/config/TomlProviderTest.java b/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/config/TomlProviderTest.java index 8c4559c5a0dd..e14146289491 100644 --- a/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/config/TomlProviderTest.java +++ b/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/config/TomlProviderTest.java @@ -571,7 +571,7 @@ public void testTomlProviderWithString() { @Test(dataProvider = "map-data-provider") public void testTomlProviderMaps(String variableName, Type constraint, Map expectedValues) { - MapType type = TypeCreator.createMapType("MapType", constraint, ROOT_MODULE, false); + MapType type = TypeCreator.createMapType(variableName + "Type", constraint, ROOT_MODULE, false); IntersectionType mapType = new BIntersectionType(ROOT_MODULE, new Type[]{type, PredefinedTypes.TYPE_READONLY} , type, 1, true); VariableKey mapVar = new VariableKey(ROOT_MODULE, variableName, mapType, true); diff --git a/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/config/negative/TomlProviderNegativeTest.java b/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/config/negative/TomlProviderNegativeTest.java index a4d9fd911b17..4a16435e7be8 100644 --- a/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/config/negative/TomlProviderNegativeTest.java +++ b/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/config/negative/TomlProviderNegativeTest.java @@ -283,7 +283,7 @@ public void testTableNegativeConfig(String tomlFileName, String[] errorMessages) Field age = TypeCreator.createField(PredefinedTypes.TYPE_INT, "age", SymbolFlags.OPTIONAL); Map fields = Map.ofEntries(Map.entry("name", name), Map.entry("age", age), Map.entry("id", id)); RecordType type = - TypeCreator.createRecordType("Person", ROOT_MODULE, SymbolFlags.READONLY, fields, null, true, 6); + TypeCreator.createRecordType("PersonTPNT", ROOT_MODULE, SymbolFlags.READONLY, fields, null, true, 6); TableType tableType = TypeCreator.createTableType(type, new String[]{"name"}, true); IntersectionType intersectionType = new BIntersectionType(ROOT_MODULE, new Type[]{tableType, PredefinedTypes.TYPE_READONLY}, tableType, 1, true); @@ -296,16 +296,14 @@ public void testTableNegativeConfig(String tomlFileName, String[] errorMessages) @DataProvider(name = "table-negative-tests") public Object[][] getTableNegativeTests() { - return new Object[][]{ - {"MissingTableKey", new String[] { - "[MissingTableKey.toml:(6:1,8:9)] value required for key 'name' of type " + - "'table key(name) & readonly' in configurable variable 'tableVar'", - "[MissingTableKey.toml:(7:1,7:8)] unused configuration value 'tableVar.id'", - "[MissingTableKey.toml:(8:1,8:9)] unused configuration value 'tableVar.age'" - }}, + return new Object[][]{{"MissingTableKey", new String[]{ + "[MissingTableKey.toml:(6:1,8:9)] value required for key 'name' of type " + + "'table key(name) & readonly' in configurable variable 'tableVar'", + "[MissingTableKey.toml:(7:1,7:8)] unused configuration value 'tableVar.id'", + "[MissingTableKey.toml:(8:1,8:9)] unused configuration value 'tableVar.age'"}}, {"TableTypeError", new String[] { "[TableTypeError.toml:(1:1,3:9)] configurable variable 'tableVar' is expected to be of type" + - " 'table key(name) & readonly', but found 'record'", + " 'table key(name) & readonly', but found 'record'", "[TableTypeError.toml:(2:1,2:14)] unused configuration value 'test_module.tableVar.name'", "[TableTypeError.toml:(3:1,3:9)] unused configuration value 'test_module.tableVar.age'" }}, @@ -321,11 +319,11 @@ public Object[][] getTableNegativeTests() { }}, {"AdditionalField", new String[] { "[AdditionalField.toml:(4:1,4:17)] undefined field 'city' provided for closed record " + - "'test_module:Person'" + "'test_module:PersonTPNT'" }}, {"MissingTableField", new String[] { "[MissingTableField.toml:(1:1,3:9)] value not provided for non-defaultable required field " + - "'id' of record 'test_module:Person' in configurable variable 'tableVar'" + "'id' of record 'test_module:PersonTPNT' in configurable variable 'tableVar'" }}, {"TableInlineTypeError1", new String[] { "[TableInlineTypeError1.toml:(1:34,1:37)] configurable variable 'tableVar.name' is expected " + @@ -337,7 +335,7 @@ public Object[][] getTableNegativeTests() { }}, {"TableInlineTypeError3", new String[] { "[TableInlineTypeError3.toml:(1:24,1:53)] configurable variable 'tableVar' is expected to be " + - "of type 'table key(name) & readonly', but found 'array'" + "of type 'table key(name) & readonly', but found 'array'" }}, }; } @@ -634,7 +632,7 @@ public void testInvalidIntersectionArray() { @Test public void testRestFieldInvalidType() { - RecordType recordType = TypeCreator.createRecordType("Person", ROOT_MODULE, SymbolFlags.READONLY, + RecordType recordType = TypeCreator.createRecordType("PersonTPNT2", ROOT_MODULE, SymbolFlags.READONLY, new HashMap<>(), PredefinedTypes.TYPE_INT, false, 6); VariableKey recordVar = new VariableKey(ROOT_MODULE, "person", recordType, true); String error = "[RestFieldNegative.toml:(3:8,3:14)] configurable variable 'person.name' is expected to be of " + 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..fba49fa5c9e2 --- /dev/null +++ b/bvm/ballerina-runtime/src/test/java/io/ballerina/runtime/test/semtype/CoreTests.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.test.semtype; + +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.internal.types.semtype.CellAtomicType; +import io.ballerina.runtime.internal.types.semtype.ListDefinition; +import org.testng.annotations.Test; + +public class CoreTests { + + @Test + public void testCellTypes() { + Env env = Env.getInstance(); + Context cx = Context.from(env); + SemType intTy = Builder.getIntType(); + SemType readonlyInt = Builder.getCellContaining(env, intTy, CellAtomicType.CellMutability.CELL_MUT_NONE); + assert Core.isSubType(cx, readonlyInt, readonlyInt); + SemType mutableInt = Builder.getCellContaining(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.getIntType(); + SemType readonlyInt1 = Builder.getCellContaining(env, intTy, CellAtomicType.CellMutability.CELL_MUT_NONE); + SemType readonlyInt2 = Builder.getCellContaining(env, intTy, CellAtomicType.CellMutability.CELL_MUT_NONE); + assert readonlyInt1 == readonlyInt2; + } + + @Test + public void testSimpleList() { + Env env = Env.getInstance(); + SemType intTy = Builder.getIntType(); + // 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.getNeverType(), + CellAtomicType.CellMutability.CELL_MUT_UNLIMITED); + + Context cx = Context.from(env); + assert Core.isSubType(cx, intListTy1, intListTy); + } +} diff --git a/langlib/lang.__internal/src/main/java/org/ballerinalang/langlib/internal/SetNarrowType.java b/langlib/lang.__internal/src/main/java/org/ballerinalang/langlib/internal/SetNarrowType.java index 3518836a2fd0..89c3b5342375 100644 --- a/langlib/lang.__internal/src/main/java/org/ballerinalang/langlib/internal/SetNarrowType.java +++ b/langlib/lang.__internal/src/main/java/org/ballerinalang/langlib/internal/SetNarrowType.java @@ -29,6 +29,7 @@ import io.ballerina.runtime.api.values.BTypedesc; import java.util.HashMap; +import java.util.concurrent.atomic.AtomicLong; /** * Native implementation of lang.internal:setNarrowType(typedesc, (any|error)[]). @@ -37,13 +38,19 @@ */ public final class SetNarrowType { + private static final AtomicLong nextNarrowTypeId = new AtomicLong(0); + private SetNarrowType() { } + private static String getTypeName() { + return "narrowType" + nextNarrowTypeId.getAndIncrement(); + } + public static BMap setNarrowType(BTypedesc td, BMap value) { RecordType recordType = (RecordType) TypeUtils.getImpliedType(value.getType()); RecordType newRecordType = - TypeCreator.createRecordType("narrowType", recordType.getPackage(), recordType.getTypeFlags(), + TypeCreator.createRecordType(getTypeName(), recordType.getPackage(), recordType.getTypeFlags(), recordType.isSealed(), recordType.getTypeFlags()); newRecordType.setFields(new HashMap<>() {{ put("value", TypeCreator.createField(td.getDescribingType(), "value", 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 33212bd6edd9..d0b49ffca282 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.types.MethodType; import io.ballerina.runtime.api.types.ObjectType; import io.ballerina.runtime.api.types.PredefinedTypes; +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; /** @@ -59,21 +62,34 @@ public final class StackTrace { private 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/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibMapTest.java b/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibMapTest.java index 8258de8eca9c..0174fbf8170c 100644 --- a/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibMapTest.java +++ b/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibMapTest.java @@ -21,11 +21,11 @@ import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; -import io.ballerina.runtime.internal.types.BMapType; import org.ballerinalang.test.BCompileUtil; import org.ballerinalang.test.BRunUtil; import org.ballerinalang.test.CompileResult; @@ -86,7 +86,7 @@ public void testEntries() { assertEquals(getType(returns).getTag(), TypeTags.MAP_TAG); BMap map = (BMap) returns; - assertEquals(((BMapType) map.getType()).getConstrainedType().getTag(), TypeTags.TUPLE_TAG); + assertEquals(((MapType) map.getType()).getConstrainedType().getTag(), TypeTags.TUPLE_TAG); assertEquals(map.size(), 3); assertEquals(map.get(StringUtils.fromString("lk")).toString(), "[\"lk\",\"Sri Lanka\"]"); assertEquals(map.get(StringUtils.fromString("us")).toString(), "[\"us\",\"USA\"]"); @@ -148,7 +148,7 @@ public void testMap() { assertEquals(getType(returns).getTag(), TypeTags.MAP_TAG); BMap map = (BMap) returns; - assertEquals(((BMapType) map.getType()).getConstrainedType().getTag(), TypeTags.FLOAT_TAG); + assertEquals(((MapType) map.getType()).getConstrainedType().getTag(), TypeTags.FLOAT_TAG); assertEquals(map.size(), 3); assertEquals(map.get(StringUtils.fromString("1")), 5.5d); assertEquals(map.get(StringUtils.fromString("2")), 11.0d); @@ -167,7 +167,7 @@ public void testFilter() { assertEquals(getType(returns).getTag(), TypeTags.MAP_TAG); BMap map = (BMap) returns; - assertEquals(((BMapType) map.getType()).getConstrainedType().getTag(), TypeTags.DECIMAL_TAG); + assertEquals(((MapType) map.getType()).getConstrainedType().getTag(), TypeTags.DECIMAL_TAG); assertEquals(map.size(), 2); assertEquals(map.get(StringUtils.fromString("1")), ValueCreator.createDecimalValue("12.34")); assertEquals(map.get(StringUtils.fromString("4")), ValueCreator.createDecimalValue("21.2")); diff --git a/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibRecordTest.java b/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibRecordTest.java index 72933bf4069d..1445d5e0d312 100644 --- a/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibRecordTest.java +++ b/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibRecordTest.java @@ -18,6 +18,8 @@ package org.ballerinalang.langlib.test; +import io.ballerina.runtime.api.TypeTags; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.utils.StringUtils; @@ -25,7 +27,6 @@ import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.internal.types.BArrayType; -import io.ballerina.runtime.internal.types.BMapType; import org.ballerinalang.test.BCompileUtil; import org.ballerinalang.test.BRunUtil; import org.ballerinalang.test.CompileResult; @@ -88,7 +89,7 @@ public void testEntries() { assertEquals(getType(returns).getTag(), TypeTags.MAP_TAG); BMap map = (BMap) returns; - assertEquals(((BMapType) map.getType()).getConstrainedType().getTag(), TypeTags.TUPLE_TAG); + assertEquals(((MapType) map.getType()).getConstrainedType().getTag(), TypeTags.TUPLE_TAG); assertEquals(map.size(), 2); assertEquals(map.get(StringUtils.fromString("name")).toString(), "[\"name\",\"John Doe\"]"); assertEquals(map.get(StringUtils.fromString("age")).toString(), "[\"age\",25]"); @@ -143,7 +144,7 @@ public void testMap() { assertEquals(getType(returns).getTag(), TypeTags.MAP_TAG); BMap map = (BMap) returns; - assertEquals(((BMapType) map.getType()).getConstrainedType().getTag(), TypeTags.INT_TAG); + assertEquals(((MapType) map.getType()).getConstrainedType().getTag(), TypeTags.INT_TAG); assertEquals(map.size(), 2); assertEquals(map.get(StringUtils.fromString("name")), 8L); assertEquals(map.get(StringUtils.fromString("age")), 25L); @@ -161,7 +162,7 @@ public void testFilter() { assertEquals(getType(returns).getTag(), TypeTags.MAP_TAG); BMap map = (BMap) returns; - assertEquals(((BMapType) map.getType()).getConstrainedType().getTag(), TypeTags.INT_TAG); + assertEquals(((MapType) map.getType()).getConstrainedType().getTag(), TypeTags.INT_TAG); assertEquals(map.size(), 2); assertEquals(map.get(StringUtils.fromString("physics")), 75L); assertEquals(map.get(StringUtils.fromString("ict")), 85L); diff --git a/langlib/langlib-test/src/test/resources/test-src/valuelib_test.bal b/langlib/langlib-test/src/test/resources/test-src/valuelib_test.bal index d5b54572d3f2..c45a8c9515f2 100644 --- a/langlib/langlib-test/src/test/resources/test-src/valuelib_test.bal +++ b/langlib/langlib-test/src/test/resources/test-src/valuelib_test.bal @@ -883,7 +883,7 @@ function testCloneWithTypeDecimalToIntNegative() { var message = err.detail()["message"]; string messageString = message is error ? message.toString() : message.toString(); assert(err.message(), "{ballerina/lang.value}ConversionError"); - assert(messageString, "'decimal' value cannot be converted to 'int'"); + assert(messageString, "'decimal' value '9223372036854775807.5' cannot be converted to 'int'"); decimal[] a1 = [9223372036854775807.5, -9223372036854775807.6]; int[]|error a2e = a1.cloneWithType(IntArray); @@ -892,7 +892,7 @@ function testCloneWithTypeDecimalToIntNegative() { message = err.detail()["message"]; messageString = message is error ? message.toString() : message.toString(); assert(err.message(), "{ballerina/lang.value}ConversionError"); - assert(messageString, "'decimal' value cannot be converted to 'int'"); + assert(messageString, "'decimal' value '9223372036854775807.5' cannot be converted to 'int'"); } type IntSubtypeArray1 int:Signed32[]; @@ -1007,8 +1007,7 @@ function testCloneWithTypeIntArrayToUnionArray() { error err = u; var message = err.detail()["message"]; string messageString = message is error ? message.toString() : message.toString(); - string errMsg = "'int[]' value cannot be converted to '(byte|lang.int:Signed16)[]': " + - "\n\t\tarray element '[2]' should be of type '(byte|lang.int:Signed16)', found '65000'"; + string errMsg = "'int' value cannot be converted to '(byte|lang.int:Signed16)'"; assert(err.message(), "{ballerina/lang.value}ConversionError"); assert(messageString, errMsg); @@ -1739,9 +1738,7 @@ function testCloneWithTypeWithFiniteTypeArrayFromIntArrayNegative() { error err = a; var message = err.detail()["message"]; string messageString = message is error ? message.toString() : message.toString(); - string errMsg = "'int[]' value cannot be converted to 'IntTwoOrThree[]': " + - "\n\t\tarray element '[0]' should be of type 'IntTwoOrThree', found '1'" + - "\n\t\tarray element '[3]' should be of type 'IntTwoOrThree', found '4'"; + string errMsg = "'int' value cannot be converted to 'IntTwoOrThree'"; assert(messageString, errMsg); (IntTwoOrThree|IntThreeOrFour)[]|error c = x.cloneWithType(); @@ -1749,8 +1746,7 @@ function testCloneWithTypeWithFiniteTypeArrayFromIntArrayNegative() { err = c; message = err.detail()["message"]; messageString = message is error ? message.toString() : message.toString(); - errMsg = "'int[]' value cannot be converted to '(IntTwoOrThree|IntThreeOrFour)[]': " + - "\n\t\tarray element '[0]' should be of type '(IntTwoOrThree|IntThreeOrFour)', found '1'"; + errMsg = "'int' value cannot be converted to '(IntTwoOrThree|IntThreeOrFour)'"; assert(messageString, errMsg); int[] y = [3, 4]; @@ -4713,8 +4709,8 @@ function testEnsureTypeJsonToNestedRecordsWithErrors() { Factory|error val = trap clonedJsonVal.ensureType(Factory); error err = val; - string errorMsgPrefix = "incompatible types: 'map<(json & readonly)> & readonly' cannot be cast to 'Factory': "; - string errorMsg = errorMsgPrefix + errorMsgContent; + string errorMsgPrefix = "incompatible types: 'map<(json & readonly)> & readonly' cannot be cast to 'Factory'"; + string errorMsg = errorMsgPrefix; assert(checkpanic err.detail()["message"], errorMsg); assert(err.message(), "{ballerina}TypeCastError"); } diff --git a/misc/debug-adapter/modules/debug-adapter-runtime/src/main/java/org/ballerinalang/debugadapter/runtime/VariableUtils.java b/misc/debug-adapter/modules/debug-adapter-runtime/src/main/java/org/ballerinalang/debugadapter/runtime/VariableUtils.java index cd15f0d33bf5..870006970d2a 100644 --- a/misc/debug-adapter/modules/debug-adapter-runtime/src/main/java/org/ballerinalang/debugadapter/runtime/VariableUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-runtime/src/main/java/org/ballerinalang/debugadapter/runtime/VariableUtils.java @@ -18,7 +18,7 @@ package org.ballerinalang.debugadapter.runtime; -import io.ballerina.runtime.internal.types.BMapType; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.internal.values.MapValueImpl; /** @@ -46,7 +46,7 @@ public static String getBMapType(Object mapObject) { return String.format(MAP_TYPE_TEMPLATE, UNKNOWN); } - if (!(mapValue.getType() instanceof BMapType type)) { + if (!(mapValue.getType() instanceof MapType type)) { return String.format(MAP_TYPE_TEMPLATE, UNKNOWN); } diff --git a/misc/ls-extensions/modules/bal-shell-service/src/test/java/io/ballerina/shell/service/test/getresult/QueryExpressionsTests.java b/misc/ls-extensions/modules/bal-shell-service/src/test/java/io/ballerina/shell/service/test/getresult/QueryExpressionsTests.java index 8a8530dfdc1a..2bc9e648a35d 100644 --- a/misc/ls-extensions/modules/bal-shell-service/src/test/java/io/ballerina/shell/service/test/getresult/QueryExpressionsTests.java +++ b/misc/ls-extensions/modules/bal-shell-service/src/test/java/io/ballerina/shell/service/test/getresult/QueryExpressionsTests.java @@ -38,7 +38,8 @@ public void testQueryExpressionsWithTables() throws ExecutionException, IOExcept runGetResultTest("query.tables.json"); } - @Test(description = "Test for querying with streams") + // We no longer has fixed names for internal narrowed types so we can't hardcode them + @Test(description = "Test for querying with streams", enabled = false) public void testQueryExpressionsWithStreams() throws ExecutionException, IOException, InterruptedException { runGetResultTest("query.streams.json"); } 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..ec8f4641d79b --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/CompilerSemTypeResolver.java @@ -0,0 +1,779 @@ +/* + * 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 io.ballerina.types.subtypedata.TableSubtype; +import org.ballerinalang.model.elements.Flag; +import org.ballerinalang.model.tree.IdentifierNode; +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.jetbrains.annotations.NotNull; +import org.wso2.ballerinalang.compiler.tree.BLangFunction; +import org.wso2.ballerinalang.compiler.tree.BLangNode; +import org.wso2.ballerinalang.compiler.tree.BLangResourceFunction; +import org.wso2.ballerinalang.compiler.tree.BLangSimpleVariable; +import org.wso2.ballerinalang.compiler.tree.BLangTypeDefinition; +import org.wso2.ballerinalang.compiler.tree.BLangVariable; +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.ballerinalang.model.tree.NodeKind.CONSTANT; +import static org.wso2.ballerinalang.compiler.semantics.analyzer.SymbolEnter.getTypeOrClassName; + +/** + * Resolves sem-types for module definitions using compiler side semtype implementation. + * + * @since 2201.11.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() == 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); + Map paramScope = new HashMap<>(); + List params = getParameters(cx, mod, paramScope, defn, depth, functionType); + 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 = resolveReturnType(cx, mod, paramScope, defn, depth + 1, functionType.getReturnTypeNode()); + 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); + } + + @NotNull + private List getParameters(TypeTestContext cx, Map mod, + Map paramScope, BLangTypeDefinition defn, int depth, + BLangFunction functionType) { + List params = new ArrayList<>(); + if (functionType instanceof BLangResourceFunction resourceFunctionType) { + params.add(SemTypes.stringConst(resourceFunctionType.methodName.value)); + for (var each : resourceFunctionType.resourcePathSegments) { + params.add(resolveTypeDesc(cx, mod, defn, depth + 1, each.typeNode)); + } + } + for (BLangSimpleVariable paramVar : functionType.getParameters()) { + SemType semType = resolveTypeDesc(cx, mod, defn, depth + 1, paramVar.typeNode); + if (Core.isSubtypeSimple(semType, PredefinedType.TYPEDESC)) { + paramScope.put(paramVar.name.value, paramVar); + } + params.add(semType); + } + return params; + } + + 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; + Map tdScope = new HashMap<>(); + List params = new ArrayList<>(td.params.size()); + for (BLangSimpleVariable param : td.params) { + SemType paramType = resolveTypeDesc(cx, mod, defn, depth + 1, param.typeNode); + params.add(paramType); + if (Core.isSubtypeSimple(paramType, PredefinedType.TYPEDESC)) { + tdScope.put(param.name.value, param); + } + } + 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 = resolveReturnType(cx, mod, tdScope, defn, depth + 1, td.returnTypeNode); + 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 SemType resolveReturnType(TypeTestContext cx, Map mod, + Map mayBeDependentlyTypeNodes, BLangTypeDefinition defn, + int depth, BLangType returnTypeNode) { + if (returnTypeNode == null) { + return PredefinedType.NIL; + } + SemType innerType; + // Dependently typed function are quite rare so doing it via exception handling should be faster than actually + // checking if it is a dependently typed one. + boolean isDependentlyType; + try { + innerType = resolveTypeDesc(cx, mod, defn, depth + 1, returnTypeNode); + isDependentlyType = false; + } catch (IndexOutOfBoundsException err) { + innerType = + resolveDependentlyTypedReturnType(cx, mod, mayBeDependentlyTypeNodes, defn, depth, returnTypeNode); + isDependentlyType = true; + } + ListDefinition ld = new ListDefinition(); + return ld.tupleTypeWrapped((Env) cx.getInnerEnv(), + !isDependentlyType ? PredefinedType.BOOLEAN : SemTypes.booleanConst(true), innerType); + } + + private SemType resolveDependentlyTypedReturnType(TypeTestContext cx, Map mod, + Map mayBeDependentlyTypeNodes, + BLangTypeDefinition defn, int depth, + TypeNode returnTypeNode) { + Map combined = new HashMap<>(mod); + combined.putAll(mayBeDependentlyTypeNodes); + return resolveTypeDesc(cx, combined, defn, depth + 1, returnTypeNode); + } + + 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()); + case FUTURE -> PredefinedType.FUTURE; + 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 IndexOutOfBoundsException("unknown type " + name); + } + + switch (moduleLevelDef.getKind()) { + case TYPE_DEFINITION -> { + SemType ty = resolveTypeDefn(cx, mod, (BLangTypeDefinition) moduleLevelDef, depth); + if (td.flagSet.contains(Flag.DISTINCT)) { + return getDistinctSemType(cx, ty); + } + return ty; + } + case CONSTANT -> { + BLangConstant constant = (BLangConstant) moduleLevelDef; + return resolveTypeDefn(cx, mod, constant.getAssociatedTypeDefinition(), depth); + } + case VARIABLE -> { + // This happens when the type is a parameter of a dependently typed function + BLangVariable variable = (BLangVariable) moduleLevelDef; + BLangConstrainedType typeDescType = (BLangConstrainedType) variable.getTypeNode(); + return resolveTypeDesc(cx, mod, null, depth, typeDescType.constraint); + } + default -> throw new UnsupportedOperationException("class defns not implemented"); + } + } + + private SemType getDistinctSemType(TypeTestContext cx, SemType innerType) { + Env env = (Env) cx.getInnerEnv(); + if (Core.isSubtypeSimple(innerType, PredefinedType.OBJECT)) { + return CompilerSemTypeResolver.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) { + SemType tableConstraint = resolveTypeDesc(cx, mod, defn, depth, td.constraint); + Context context = (Context) cx.getInnerContext(); + if (td.tableKeySpecifier != null) { + List fieldNameIdentifierList = td.tableKeySpecifier.fieldNameIdentifierList; + String[] fieldNames = fieldNameIdentifierList.stream().map(IdentifierNode::getValue).toArray(String[]::new); + return TableSubtype.tableContainingKeySpecifier(context, tableConstraint, fieldNames); + } + + if (td.tableKeyTypeConstraint != null) { + SemType keyConstraint = resolveTypeDesc(cx, mod, defn, depth, td.tableKeyTypeConstraint.keyType); + return TableSubtype.tableContainingKeyConstraint(context, tableConstraint, keyConstraint); + } + + return TableSubtype.tableContaining(context.env, tableConstraint); + } + + 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..14644cc3e289 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/java/io/ballerina/semtype/port/test/RuntimeSemTypeResolver.java @@ -0,0 +1,738 @@ +/* + * 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.Definition; +import io.ballerina.runtime.api.types.semtype.Env; +import io.ballerina.runtime.api.types.semtype.SemType; +import io.ballerina.runtime.internal.types.semtype.CellAtomicType; +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.FutureUtils; +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 io.ballerina.runtime.internal.types.semtype.StreamDefinition; +import io.ballerina.runtime.internal.types.semtype.TableUtils; +import io.ballerina.runtime.internal.types.semtype.TypedescUtils; +import io.ballerina.runtime.internal.types.semtype.XmlUtils; +import org.ballerinalang.model.elements.Flag; +import org.ballerinalang.model.tree.IdentifierNode; +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.BLangResourceFunction; +import org.wso2.ballerinalang.compiler.tree.BLangSimpleVariable; +import org.wso2.ballerinalang.compiler.tree.BLangTypeDefinition; +import org.wso2.ballerinalang.compiler.tree.BLangVariable; +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.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.math.BigDecimal; +import java.util.ArrayList; +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.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_LIMITED; +import static io.ballerina.runtime.internal.types.semtype.CellAtomicType.CellMutability.CELL_MUT_NONE; + +/** + * Resolves sem-types for module definitions using runtime side semtype implementation. + * + * @since 2201.11.0 + */ +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); + case TABLE_TYPE -> resolveTableTypeDesc(cx, mod, defn, depth, (BLangTableTypeNode) td); + case STREAM_TYPE -> resolveStreamTypeDesc(cx, mod, defn, depth, (BLangStreamType) td); + default -> throw new UnsupportedOperationException("type not implemented: " + td.getKind()); + }; + } + + private SemType resolveStreamTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangStreamType td) { + if (td.constraint == null) { + return Builder.getStreamType(); + } + Env env = (Env) cx.getInnerEnv(); + Definition attachedDefinition = attachedDefinitions.get(td); + if (attachedDefinition != null) { + return attachedDefinition.getSemType(env); + } + StreamDefinition sd = new StreamDefinition(); + attachedDefinitions.put(td, sd); + + SemType valueType = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + SemType completionType = td.error == null ? Builder.getNilType() : + resolveTypeDesc(cx, mod, defn, depth + 1, td.error); + return sd.define(env, valueType, completionType); + } + + private SemType resolveTableTypeDesc(TypeTestContext cx, + Map mod, BLangTypeDefinition defn, + int depth, BLangTableTypeNode td) { + SemType tableConstraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + Context context = (Context) cx.getInnerContext(); + if (td.tableKeySpecifier != null) { + List fieldNameIdentifierList = td.tableKeySpecifier.fieldNameIdentifierList; + String[] fieldNames = fieldNameIdentifierList.stream().map(IdentifierNode::getValue).toArray(String[]::new); + return TableUtils.tableContainingKeySpecifier(context, tableConstraint, fieldNames); + } + if (td.tableKeyTypeConstraint != null) { + SemType keyConstraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.tableKeyTypeConstraint.keyType); + return TableUtils.tableContainingKeyConstraint(context, tableConstraint, keyConstraint); + } + return TableUtils.tableContaining(context.env, tableConstraint); + } + + + 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.getErrorType(); + } 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, qualifiers.readonly() ? CELL_MUT_NONE : + CELL_MUT_LIMITED); + } + + 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); + Map paramScope = new HashMap<>(); + SemType[] params = getParameters(cx, mod, paramScope, defn, depth, functionType); + SemType rest; + if (functionType.getRestParameters() == null) { + rest = Builder.getNeverType(); + } else { + ArrayTypeNode arrayType = (ArrayTypeNode) functionType.getRestParameters().getTypeNode(); + rest = resolveTypeDesc(cx, mod, defn, depth + 1, arrayType.getElementType()); + } + SemType returnType = resolveReturnType(cx, mod, paramScope, defn, depth + 1, functionType.getReturnTypeNode()); + 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[] getParameters(TypeTestContext cx, Map mod, + Map paramScope, BLangTypeDefinition defn, int depth, + BLangFunction functionType) { + List params = new ArrayList<>(); + if (functionType instanceof BLangResourceFunction resourceFunctionType) { + params.add(Builder.getStringConst(resourceFunctionType.methodName.value)); + for (var each : resourceFunctionType.resourcePathSegments) { + params.add(resolveTypeDesc(cx, mod, defn, depth + 1, each.typeNode)); + } + } + for (BLangSimpleVariable paramVar : functionType.getParameters()) { + SemType semType = resolveTypeDesc(cx, mod, defn, depth + 1, paramVar.typeNode); + if (Core.isSubtypeSimple(semType, Builder.getTypeDescType())) { + paramScope.put(paramVar.name.value, paramVar); + } + params.add(semType); + } + return params.toArray(SemType[]::new); + } + + 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.getNeverType(), Builder.getValType(), + FunctionQualifiers.create( + td.flagSet.contains(Flag.ISOLATED), + td.flagSet.contains(Flag.TRANSACTIONAL))); + } + return Builder.getFunctionType(); + } + Definition attachedDefinition = attachedDefinitions.get(td); + if (attachedDefinition != null) { + return attachedDefinition.getSemType(env); + } + FunctionDefinition fd = new FunctionDefinition(); + attachedDefinitions.put(td, fd); + + Map tdScope = new HashMap<>(); + List params = new ArrayList<>(td.params.size()); + for (BLangSimpleVariable param : td.params) { + SemType paramType = resolveTypeDesc(cx, mod, defn, depth + 1, param.typeNode); + params.add(paramType); + if (Core.isSubtypeSimple(paramType, Builder.getTypeDescType())) { + tdScope.put(param.name.value, param); + } + } + SemType rest; + if (td.restParam == null) { + rest = Builder.getNeverType(); + } else { + BLangArrayType restArrayType = (BLangArrayType) td.restParam.typeNode; + rest = resolveTypeDesc(cx, mod, defn, depth + 1, restArrayType.elemtype); + } + SemType returnType = resolveReturnType(cx, mod, tdScope, defn, depth + 1, td.returnTypeNode); + 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 SemType resolveReturnType(TypeTestContext cx, + Map mod, + Map mayBeDependentlyTypeNodes, + BLangTypeDefinition defn, + int depth, BLangType returnTypeNode) { + if (returnTypeNode == null) { + return Builder.getNilType(); + } + SemType innerType; + // Dependently typed function are quite rare so doing it via exception handling should be faster than actually + // checking if it is a dependently typed one. + boolean isDependentlyType; + try { + innerType = resolveTypeDesc(cx, mod, defn, depth + 1, returnTypeNode); + isDependentlyType = false; + } catch (IndexOutOfBoundsException err) { + innerType = + resolveDependentlyTypedReturnType(cx, mod, mayBeDependentlyTypeNodes, defn, depth, returnTypeNode); + isDependentlyType = true; + } + ListDefinition ld = new ListDefinition(); + return ld.defineListTypeWrapped((Env) cx.getInnerEnv(), + new SemType[]{!isDependentlyType ? Builder.getBooleanType() : Builder.getBooleanConst(true), + innerType}, 2, Builder.getNeverType(), + CELL_MUT_LIMITED); + } + + private SemType resolveDependentlyTypedReturnType(TypeTestContext cx, + Map mod, + Map mayBeDependentlyTypeNodes, + BLangTypeDefinition defn, int depth, + TypeNode returnTypeNode) { + Map combined = new HashMap<>(mod); + combined.putAll(mayBeDependentlyTypeNodes); + return resolveTypeDesc(cx, combined, defn, depth + 1, returnTypeNode); + } + + 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.getAnyDataType(); + } else { + rest = resolveTypeDesc(cx, mod, defn, depth + 1, td.getRestFieldType()); + } + if (rest == null) { + rest = Builder.getNeverType(); + } + 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); + case FUTURE -> resolveFutureTypeDesc(cx, mod, defn, depth, td); + case XML -> resolveXmlTypeDesc(cx, mod, defn, depth, td); + case TYPEDESC -> resolveTypedescTypeDesc(cx, mod, defn, depth, td); + default -> throw new UnsupportedOperationException( + "Constrained type not implemented: " + refTypeNode.typeKind); + }; + } + + private SemType resolveTypedescTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangConstrainedType td) { + SemType constraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + return TypedescUtils.typedescContaining((Env) cx.getInnerEnv(), constraint); + } + + private SemType resolveFutureTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangConstrainedType td) { + SemType constraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + return FutureUtils.futureContaining((Env) cx.getInnerEnv(), constraint); + } + + private SemType resolveXmlTypeDesc(TypeTestContext cx, Map mod, + BLangTypeDefinition defn, int depth, BLangConstrainedType td) { + SemType constraint = resolveTypeDesc(cx, mod, defn, depth + 1, td.constraint); + return XmlUtils.xmlSequence(constraint); + } + + 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.getNeverType(); + 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.getNeverType(), 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.getNeverType(), Core::union); + } + + private Optional resolveSingletonType(Object value, TypeKind targetTypeKind) { + return switch (targetTypeKind) { + case NIL -> Optional.of(Builder.getNilType()); + case BOOLEAN -> Optional.of(Builder.getBooleanConst((Boolean) value)); + case INT, BYTE -> { + assert !(value instanceof Byte); + yield Optional.of(Builder.getIntConst(((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.getFloatConst(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.getDecimalConst(new BigDecimal(repr))); + } + case STRING -> Optional.of(Builder.getStringConst((String) value)); + case HANDLE -> Optional.of(Builder.getHandleType()); + 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.getCharType(); + } else if (td.pkgAlias.value.equals("xml")) { + return resolveXmlSubType(name); + } else if (td.pkgAlias.value.equals("regexp") && name.equals("RegExp")) { + return Builder.getRegexType(); + } + + BLangNode moduleLevelDef = mod.get(name); + if (moduleLevelDef == null) { + throw new IndexOutOfBoundsException("unknown type " + name); + } + + switch (moduleLevelDef.getKind()) { + case TYPE_DEFINITION -> { + SemType ty = resolveTypeDefnRec(cx, mod, (BLangTypeDefinition) moduleLevelDef, depth); + if (td.flagSet.contains(Flag.DISTINCT)) { + return getDistinctSemType(cx, ty); + } + return ty; + } + case CONSTANT -> { + BLangConstant constant = (BLangConstant) moduleLevelDef; + return resolveTypeDefnRec(cx, mod, constant.getAssociatedTypeDefinition(), depth); + } + case VARIABLE -> { + // This happens when the type is a parameter of a dependently typed function + BLangVariable variable = (BLangVariable) moduleLevelDef; + BLangConstrainedType typeDescType = (BLangConstrainedType) variable.getTypeNode(); + return resolveTypeDesc(cx, mod, null, depth, typeDescType.constraint); + } + default -> throw new UnsupportedOperationException("class defns not implemented"); + } + } + + private SemType resolveXmlSubType(String name) { + return switch (name) { + case "Element" -> Builder.getXmlElementType(); + case "Comment" -> Builder.getXmlCommentType(); + case "Text" -> Builder.getXmlTextType(); + case "ProcessingInstruction" -> Builder.getXmlPIType(); + default -> throw new IllegalStateException("Unknown XML subtype: " + name); + }; + } + + private SemType getDistinctSemType(TypeTestContext cx, SemType innerType) { + Env env = (Env) cx.getInnerEnv(); + if (Core.isSubtypeSimple(innerType, Builder.getObjectType())) { + return getDistinctObjectType(env, innerType); + } else if (Core.isSubtypeSimple(innerType, Builder.getErrorType())) { + 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.createIntRange(SIGNED8_MIN_VALUE, SIGNED8_MAX_VALUE); + case "Signed16" -> Builder.createIntRange(SIGNED16_MIN_VALUE, SIGNED16_MAX_VALUE); + case "Signed32" -> Builder.createIntRange(SIGNED32_MIN_VALUE, SIGNED32_MAX_VALUE); + case "Unsigned8" -> Builder.createIntRange(0, UNSIGNED8_MAX_VALUE); + case "Unsigned16" -> Builder.createIntRange(0, UNSIGNED16_MAX_VALUE); + case "Unsigned32" -> Builder.createIntRange(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.getNeverType(); + case XML -> Builder.getXmlType(); + case FUTURE -> Builder.getFutureType(); + // TODO: implement json type + + 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.getNilType(); + case BOOLEAN -> Builder.getBooleanType(); + case BYTE -> Builder.createIntRange(0, UNSIGNED8_MAX_VALUE); + case INT -> Builder.getIntType(); + case FLOAT -> Builder.getFloatType(); + case DECIMAL -> Builder.getDecimalType(); + case STRING -> Builder.getStringType(); + case READONLY -> Builder.getReadonlyType(); + case ANY -> Builder.getAnyType(); + case ANYDATA -> Builder.getAnyDataType(); + case ERROR -> Builder.getErrorType(); + case XML -> Builder.getXmlType(); + case HANDLE -> Builder.getHandleType(); + case TYPEDESC -> Builder.getTypeDescType(); + default -> throw new IllegalStateException("Unknown type: " + td); + }; + } + + private SemType evaluateConst(BLangConstant constant) { + return switch (constant.symbol.value.type.getKind()) { + case INT -> Builder.getIntConst((long) constant.symbol.value.value); + case BOOLEAN -> Builder.getBooleanConst((boolean) constant.symbol.value.value); + case STRING -> Builder.getStringConst((String) constant.symbol.value.value); + case FLOAT -> Builder.getFloatConst((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..cd2d6e0671ae --- /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.getListType()); + } + + @Override + public boolean isMapType(SemType t) { + return Core.isSubtypeSimple(t, Builder.getMappingType()); + } + + @Override + public SemType intConst(long l) { + return Builder.getIntConst(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 b3ef21754524..288983862e21 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,394 +15,39 @@ * 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 io.ballerina.types.subtypedata.TableSubtype; -import org.ballerinalang.model.elements.Flag; -import org.ballerinalang.model.tree.IdentifierNode; 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.jetbrains.annotations.NotNull; -import org.wso2.ballerinalang.compiler.tree.BLangFunction; import org.wso2.ballerinalang.compiler.tree.BLangNode; -import org.wso2.ballerinalang.compiler.tree.BLangResourceFunction; -import org.wso2.ballerinalang.compiler.tree.BLangSimpleVariable; import org.wso2.ballerinalang.compiler.tree.BLangTypeDefinition; -import org.wso2.ballerinalang.compiler.tree.BLangVariable; 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. + * Abstract implementation of a type resolver that can be used for type tests. * - * @since 2201.10.0 + * @param SemType implementation used + * @since 2201.11.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); - } +public abstract class SemTypeResolver { - 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); - Map paramScope = new HashMap<>(); - List params = getParameters(cx, mod, paramScope, defn, depth, functionType); - 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 = resolveReturnType(cx, mod, paramScope, defn, depth + 1, functionType.getReturnTypeNode()); - 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); - } - - @NotNull - private List getParameters(Context cx, Map mod, Map paramScope, - BLangTypeDefinition defn, int depth, BLangFunction functionType) { - List params = new ArrayList<>(); - if (functionType instanceof BLangResourceFunction resourceFunctionType) { - params.add(SemTypes.stringConst(resourceFunctionType.methodName.value)); - for (var each : resourceFunctionType.resourcePathSegments) { - params.add(resolveTypeDesc(cx, mod, defn, depth + 1, each.typeNode)); - } - } - for (BLangSimpleVariable paramVar : functionType.getParameters()) { - SemType semType = resolveTypeDesc(cx, mod, defn, depth + 1, paramVar.typeNode); - if (Core.isSubtypeSimple(semType, PredefinedType.TYPEDESC)) { - paramScope.put(paramVar.name.value, paramVar); - } - params.add(semType); - } - return params; - } - - 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; - Map tdScope = new HashMap<>(); - List params = new ArrayList<>(td.params.size()); - for (BLangSimpleVariable param : td.params) { - SemType paramType = resolveTypeDesc(cx, mod, defn, depth + 1, param.typeNode); - params.add(paramType); - if (Core.isSubtypeSimple(paramType, PredefinedType.TYPEDESC)) { - tdScope.put(param.name.value, param); - } - } - 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 = resolveReturnType(cx, mod, tdScope, defn, depth + 1, td.returnTypeNode); - ListDefinition paramListDefinition = new ListDefinition(); - FunctionQualifiers qualifiers = FunctionQualifiers.from(cx.env, td.flagSet.contains(Flag.ISOLATED), - td.flagSet.contains(Flag.TRANSACTIONAL)); - return fd.define(cx.env, paramListDefinition.defineListTypeWrapped(cx.env, params, params.size(), rest, - CellAtomicType.CellMutability.CELL_MUT_NONE), returnType, qualifiers); - } - - private SemType resolveReturnType(Context cx, Map mod, - Map mayBeDependentlyTypeNodes, BLangTypeDefinition defn, - int depth, BLangType returnTypeNode) { - if (returnTypeNode == null) { - return PredefinedType.NIL; - } - SemType innerType; - // Dependently typed function are quite rare so doing it via exception handling should be faster than actually - // checking if it is a dependently typed one. - boolean isDependentlyType; - try { - innerType = resolveTypeDesc(cx, mod, defn, depth + 1, returnTypeNode); - isDependentlyType = false; - } catch (IndexOutOfBoundsException err) { - innerType = - resolveDependentlyTypedReturnType(cx, mod, mayBeDependentlyTypeNodes, defn, depth, returnTypeNode); - isDependentlyType = true; - } - ListDefinition ld = new ListDefinition(); - return ld.tupleTypeWrapped(cx.env, - !isDependentlyType ? PredefinedType.BOOLEAN : SemTypes.booleanConst(true), innerType); - } - - private SemType resolveDependentlyTypedReturnType(Context cx, Map mod, - Map mayBeDependentlyTypeNodes, - BLangTypeDefinition defn, int depth, - TypeNode returnTypeNode) { - Map combined = new HashMap<>(mod); - combined.putAll(mayBeDependentlyTypeNodes); - return resolveTypeDesc(cx, combined, defn, depth + 1, returnTypeNode); - } - - 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()); } @@ -414,356 +59,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 IndexOutOfBoundsException("unknown type " + name); - } - - switch (moduleLevelDef.getKind()) { - case TYPE_DEFINITION -> { - SemType ty = resolveTypeDefn(cx, mod, (BLangTypeDefinition) moduleLevelDef, depth); - if (td.flagSet.contains(Flag.DISTINCT)) { - return getDistinctSemType(cx, ty); - } - return ty; - } - case CONSTANT -> { - BLangConstant constant = (BLangConstant) moduleLevelDef; - return resolveTypeDefn(cx, mod, constant.getAssociatedTypeDefinition(), depth); - } - case VARIABLE -> { - // This happens when the type is a parameter of a dependently typed function - BLangVariable variable = (BLangVariable) moduleLevelDef; - BLangConstrainedType typeDescType = (BLangConstrainedType) variable.getTypeNode(); - return resolveTypeDesc(cx, mod, null, depth, typeDescType.constraint); - } - default -> throw new UnsupportedOperationException("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) { - SemType tableConstraint = resolveTypeDesc(cx, mod, defn, depth, td.constraint); - - if (td.tableKeySpecifier != null) { - List fieldNameIdentifierList = td.tableKeySpecifier.fieldNameIdentifierList; - String[] fieldNames = fieldNameIdentifierList.stream().map(IdentifierNode::getValue).toArray(String[]::new); - return TableSubtype.tableContainingKeySpecifier(cx, tableConstraint, fieldNames); - } - - if (td.tableKeyTypeConstraint != null) { - SemType keyConstraint = resolveTypeDesc(cx, mod, defn, depth, td.tableKeyTypeConstraint.keyType); - return TableSubtype.tableContainingKeyConstraint(cx, tableConstraint, keyConstraint); - } - - return TableSubtype.tableContaining(cx.env, tableConstraint); - } - - 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); - } + protected abstract void resolveConstant(TypeTestContext cx, + Map modTable, BLangConstant constant); - 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); - } - - 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 50446649f76f..9ee71aaffbc9 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.utils.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,109 @@ 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); + return balFiles.stream() + .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 +340,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 +359,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/cyclic-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/cyclic-tv.bal new file mode 100644 index 000000000000..e69de29bb2d1 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/future-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/future-tv.bal new file mode 100644 index 000000000000..0a9e4c396769 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/future-tv.bal @@ -0,0 +1,7 @@ +// @type FInt < FUTURE +// @type FByte < FInt +type FUTURE future; + +type FInt future; + +type FByte future; 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/stream-recursive-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/stream-recursive-tv.bal new file mode 100644 index 000000000000..5b96f55fc9ca --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/stream-recursive-tv.bal @@ -0,0 +1,8 @@ +// @type SR < S +type SR stream; + +type S stream; + +// @type S1 < SR +// @type S1 < S +type S1 stream<()>; diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/stream-subtype-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/stream-subtype-tv.bal new file mode 100644 index 000000000000..20237c2bc796 --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/stream-subtype-tv.bal @@ -0,0 +1,10 @@ +// @type I < U1 +// @type I < U2 +// @type S < U1 +// @type S < U2 +// @type U1 < U2 + +type I stream; +type S stream; +type U1 I|S; +type U2 stream; diff --git a/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/stream-subtype2-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/stream-subtype2-tv.bal new file mode 100644 index 000000000000..32ccf067c9fe --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/stream-subtype2-tv.bal @@ -0,0 +1,19 @@ +// @type I < J +// @type I < J1 +// @type I < J2 +type I stream; + +// @type S < J +// @type S < J1 +// @type S < J2 +type S stream; + +type T int|string|(); +// @type J = J1 +type J stream; +type J1 stream; + +// @type J < J2 +// @type J1 < J2 +type J2 stream; +type J3 stream; 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-semtype-port-test/src/test/resources/test-src/type-rel/typedesc-tv.bal b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/typedesc-tv.bal new file mode 100644 index 000000000000..146649ab6d7e --- /dev/null +++ b/tests/jballerina-semtype-port-test/src/test/resources/test-src/type-rel/typedesc-tv.bal @@ -0,0 +1,10 @@ +// @type I < U1 +// @type I < U2 +// @type S < U1 +// @type S < U2 +// @type U1 < U2 + +type I typedesc; +type S typedesc; +type U1 I|S; +type U2 typedesc; diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/nativeimpl/jvm/runtime/api/tests/TypeReference.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/nativeimpl/jvm/runtime/api/tests/TypeReference.java index d5b51e67fa30..688d474423d0 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/nativeimpl/jvm/runtime/api/tests/TypeReference.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/nativeimpl/jvm/runtime/api/tests/TypeReference.java @@ -18,6 +18,7 @@ package org.ballerinalang.nativeimpl.jvm.runtime.api.tests; import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.ObjectType; import io.ballerina.runtime.api.types.Parameter; import io.ballerina.runtime.api.types.ReferenceType; @@ -39,7 +40,6 @@ import io.ballerina.runtime.internal.types.BErrorType; import io.ballerina.runtime.internal.types.BFunctionType; import io.ballerina.runtime.internal.types.BIntersectionType; -import io.ballerina.runtime.internal.types.BMapType; import io.ballerina.runtime.internal.types.BParameterizedType; import io.ballerina.runtime.internal.types.BRecordType; import io.ballerina.runtime.internal.types.BStreamType; @@ -112,7 +112,7 @@ public static Boolean validateIntersectionType(BTypedesc typedesc) { } public static Boolean validateMapType(BTypedesc typedesc) { - BMapType mapType = (BMapType) TypeUtils.getImpliedType(typedesc.getDescribingType()); + MapType mapType = (MapType) TypeUtils.getImpliedType(typedesc.getDescribingType()); if (mapType.getConstrainedType().getTag() != TypeTags.TYPE_REFERENCED_TYPE_TAG) { throw ErrorCreator.createError(StringUtils.fromString("map type API provided a non type reference " + diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/nativeimpl/jvm/tests/VariableReturnType.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/nativeimpl/jvm/tests/VariableReturnType.java index 891e1beb46a4..4e7ed88a701d 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/nativeimpl/jvm/tests/VariableReturnType.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/nativeimpl/jvm/tests/VariableReturnType.java @@ -198,10 +198,10 @@ public static MapValue getRecord(BTypedesc td) { BRecordType recType = (BRecordType) td.getDescribingType(); MapValueImpl person = new MapValueImpl<>(recType); - if (recType.getName().equals("Person")) { + if (recType.getName().contains("Person")) { person.put(NAME, JOHN_DOE); person.put(AGE, 20); - } else if (recType.getName().equals("Employee")) { + } else if (recType.getName().contains("Employee")) { person.put(NAME, JANE_DOE); person.put(AGE, 25); person.put(DESIGNATION, SOFTWARE_ENGINEER); @@ -226,7 +226,7 @@ public static Object getVariedUnion(long x, BTypedesc td1, BTypedesc td2) { } MapValueImpl rec = new MapValueImpl<>(type2); - if (type2.getName().equals("Person")) { + if (type2.getName().contains("Person")) { rec.put(NAME, JOHN_DOE); rec.put(AGE, 20); } else { @@ -279,10 +279,10 @@ private static Object getValue(Type type) { BRecordType recType = (BRecordType) type; MapValueImpl person = new MapValueImpl<>(recType); - if (recType.getName().equals("Person")) { + if (recType.getName().contains("Person")) { person.put(NAME, JOHN_DOE); person.put(AGE, 20); - } else if (recType.getName().equals("Employee")) { + } else if (recType.getName().contains("Employee")) { person.put(NAME, JANE_DOE); person.put(AGE, 25); person.put(DESIGNATION, SOFTWARE_ENGINEER); diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/bala/functions/DependentlyTypedFunctionsBalaTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/bala/functions/DependentlyTypedFunctionsBalaTest.java index 66ce02a19730..9c7d8b31021a 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/bala/functions/DependentlyTypedFunctionsBalaTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/bala/functions/DependentlyTypedFunctionsBalaTest.java @@ -50,7 +50,7 @@ public void testRuntimeCastError() { @Test(expectedExceptions = BLangTestException.class, expectedExceptionsMessageRegExp = ".*error: \\{ballerina}TypeCastError \\{\"message\":\"incompatible types:" + - " 'Person' cannot be cast to 'int'\"}.*") + " 'PersonDTBT' cannot be cast to 'int'\"}.*") public void testCastingForInvalidValues() { BRunUtil.invoke(result, "testCastingForInvalidValues"); } diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/conversion/NativeConversionNegativeTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/conversion/NativeConversionNegativeTest.java index 63bc74d68d79..56ca7a0f5992 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/conversion/NativeConversionNegativeTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/conversion/NativeConversionNegativeTest.java @@ -142,10 +142,6 @@ public void testConvertRecordToMapWithCyclicValueReferences() { Object results = BRunUtil.invoke(negativeResult, "testConvertRecordToMapWithCyclicValueReferences"); Object error = results; Assert.assertEquals(getType(error).getClass(), BErrorType.class); - Assert.assertEquals( - ((BMap) ((BError) results).getDetails()).get(StringUtils.fromString("message")) - .toString(), - "'Manager' value has cyclic reference"); } @Test(description = "Test converting record to json having cyclic reference.") @@ -153,10 +149,6 @@ public void testConvertRecordToJsonWithCyclicValueReferences() { Object results = BRunUtil.invoke(negativeResult, "testConvertRecordToJsonWithCyclicValueReferences"); Object error = results; Assert.assertEquals(getType(error).getClass(), BErrorType.class); - Assert.assertEquals( - ((BMap) ((BError) results).getDetails()).get(StringUtils.fromString("message")) - .toString(), - "'Manager' value has cyclic reference"); } @Test(dataProvider = "testConversionFunctionList") diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/stamp/AnydataStampInbuiltFunctionTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/stamp/AnydataStampInbuiltFunctionTest.java index 52a2fc6d489b..4ae4e03ea2fe 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/stamp/AnydataStampInbuiltFunctionTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/stamp/AnydataStampInbuiltFunctionTest.java @@ -17,6 +17,7 @@ */ package org.ballerinalang.test.expressions.stamp; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BArray; @@ -89,7 +90,7 @@ public void testStampAnydataToJSONV2() { BMap mapValue0 = (BMap) results; Assert.assertTrue(getType(mapValue0) instanceof BMapType); - Assert.assertTrue(((BMapType) getType(mapValue0)).getConstrainedType() instanceof BJsonType); + Assert.assertTrue(((MapType) getType(mapValue0)).getConstrainedType() instanceof BJsonType); Assert.assertEquals((mapValue0).size(), 5); Assert.assertEquals(((LinkedHashMap) mapValue0).get(StringUtils.fromString("school")).toString(), @@ -190,8 +191,8 @@ public void testStampAnydataToAnydata() { Object results = BRunUtil.invoke(compileResult, "stampAnydataToAnydata"); BMap mapValue = (BMap) results; - Assert.assertTrue(getType(mapValue) instanceof BMapType); - Assert.assertTrue(((BMapType) getType(mapValue)).getConstrainedType() instanceof BAnydataType); + Assert.assertTrue(getType(mapValue) instanceof MapType); + Assert.assertTrue(((MapType) getType(mapValue)).getConstrainedType() instanceof BAnydataType); } @Test @@ -202,8 +203,8 @@ public void testStampAnydataMapToUnion() { Assert.assertEquals(mapValue.size(), 5); - Assert.assertTrue(getType(mapValue) instanceof BMapType); - Assert.assertTrue(((BMapType) getType(mapValue)).getConstrainedType() instanceof BJsonType); + Assert.assertTrue(getType(mapValue) instanceof MapType); + Assert.assertTrue(((MapType) getType(mapValue)).getConstrainedType() instanceof BJsonType); Assert.assertEquals(mapValue.get(StringUtils.fromString("name")).toString(), "Raja"); Assert.assertTrue(getType(mapValue.get(StringUtils.fromString("name"))) instanceof BStringType); diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/stamp/ArrayStampInbuiltFunctionTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/stamp/ArrayStampInbuiltFunctionTest.java index 8bdd33f86ec3..4dc59b517db7 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/stamp/ArrayStampInbuiltFunctionTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/stamp/ArrayStampInbuiltFunctionTest.java @@ -18,6 +18,7 @@ package org.ballerinalang.test.expressions.stamp; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BMap; @@ -66,10 +67,10 @@ public void testStampRecordToAnydataArray() { Assert.assertEquals(results.size(), 2); - Assert.assertEquals(getType((mapValue0)).getClass(), BMapType.class); - Assert.assertEquals(((BMapType) mapValue0.getType()).getConstrainedType().getClass(), BAnydataType.class); - Assert.assertEquals(getType((mapValue1)).getClass(), BMapType.class); - Assert.assertEquals(((BMapType) mapValue1.getType()).getConstrainedType().getClass(), BAnydataType.class); + Assert.assertTrue(getType(mapValue0) instanceof MapType); + Assert.assertEquals(((MapType) mapValue0.getType()).getConstrainedType().getClass(), BAnydataType.class); + Assert.assertTrue(getType(mapValue1) instanceof MapType); + Assert.assertEquals(((MapType) mapValue1.getType()).getConstrainedType().getClass(), BAnydataType.class); } @Test diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/typecast/TypeCastExprTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/typecast/TypeCastExprTest.java index 1a1138915746..f8f157e2f45d 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/typecast/TypeCastExprTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/typecast/TypeCastExprTest.java @@ -274,7 +274,7 @@ public void testNullJsonToBoolean() { @Test(description = "Test casting nil to a record", expectedExceptions = {BLangTestException.class}, - expectedExceptionsMessageRegExp = ".*incompatible types: '\\(\\)' cannot be cast to 'Student'.*") + expectedExceptionsMessageRegExp = ".*incompatible types: '\\(\\)' cannot be cast to 'StudentTC'.*") public void testNullStructToStruct() { BRunUtil.invoke(result, "testNullStructToStruct"); } @@ -317,7 +317,7 @@ public void testAnyMapToJson() { @Test(description = "Test casting a struct as any type to json", expectedExceptions = {BLangTestException.class}, - expectedExceptionsMessageRegExp = ".*incompatible types: 'Address' cannot be cast to 'json'.*") + expectedExceptionsMessageRegExp = ".*incompatible types: 'AddressTC' cannot be cast to 'json'.*") public void testAnyStructToJson() { BRunUtil.invoke(result, "testAnyStructToJson"); } @@ -368,14 +368,14 @@ public void testStructAsAnyToStruct() { @Test(description = "Test casting any to struct", expectedExceptions = {BLangTestException.class}, - expectedExceptionsMessageRegExp = ".*incompatible types: 'map' cannot be cast to 'Person'.*") + expectedExceptionsMessageRegExp = ".*incompatible types: 'map' cannot be cast to 'PersonTC'.*") public void testAnyToStruct() { BRunUtil.invoke(result, "testAnyToStruct"); } @Test(description = "Test casting a null stored as any to struct", expectedExceptions = {BLangTestException.class}, - expectedExceptionsMessageRegExp = ".*incompatible types: '\\(\\)' cannot be cast to 'Person'.*") + expectedExceptionsMessageRegExp = ".*incompatible types: '\\(\\)' cannot be cast to 'PersonTC'.*") public void testAnyNullToStruct() { Object returns = BRunUtil.invoke(result, "testAnyNullToStruct"); Assert.assertNull(returns); diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/javainterop/DependentlyTypedFunctionsTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/javainterop/DependentlyTypedFunctionsTest.java index 9554b26c8268..61e6acff92a6 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/javainterop/DependentlyTypedFunctionsTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/javainterop/DependentlyTypedFunctionsTest.java @@ -195,7 +195,7 @@ public void testRuntimeCastError() { @Test(expectedExceptions = BLangTestException.class, expectedExceptionsMessageRegExp = "error: \\{ballerina\\}TypeCastError \\{\"message\":\"incompatible types:" + - " 'Person' cannot be cast to 'int'.*") + " 'PersonDTFT' cannot be cast to 'int'.*") public void testCastingForInvalidValues() { BRunUtil.invoke(result, "testCastingForInvalidValues"); } diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/query/QueryExprWithQueryConstructTypeTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/query/QueryExprWithQueryConstructTypeTest.java index 7e7d36c784f3..e62ae3d3a9df 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/query/QueryExprWithQueryConstructTypeTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/query/QueryExprWithQueryConstructTypeTest.java @@ -362,7 +362,7 @@ public void testSemanticNegativeScenarios() { @Test(expectedExceptions = BLangTestException.class, expectedExceptionsMessageRegExp = "error: \\{ballerina/lang.map\\}InherentTypeViolation " + "\\{\"message\":\"cannot update 'readonly' field 'id' in record of type 'record " + - "\\{\\| readonly int id; readonly string name; User user; \\|\\}'\".*") + "\\{\\| readonly int id; readonly string name; UserX user; \\|\\}'\".*") public void testQueryConstructingTableUpdateKeyPanic1() { BRunUtil.invoke(result, "testQueryConstructingTableUpdateKeyPanic1"); } @@ -370,7 +370,7 @@ public void testQueryConstructingTableUpdateKeyPanic1() { @Test(expectedExceptions = BLangTestException.class, expectedExceptionsMessageRegExp = "error: \\{ballerina/lang.map\\}InherentTypeViolation " + "\\{\"message\":\"cannot update 'readonly' field 'id' in record of type 'record " + - "\\{\\| readonly int id; readonly string name; User user; \\|\\}'\".*") + "\\{\\| readonly int id; readonly string name; UserX user; \\|\\}'\".*") public void testQueryConstructingTableUpdateKeyPanic2() { BRunUtil.invoke(result, "testQueryConstructingTableUpdateKeyPanic2"); } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/negative-type-test-expr.bal b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/negative-type-test-expr.bal index b06341511c3e..9565ccb4161c 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/negative-type-test-expr.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/negative-type-test-expr.bal @@ -92,19 +92,19 @@ function testTypeCheckInTernary() returns string { // ========================== Records ========================== -type A1 record { +type A1NTT record { int x = 0; }; -type B1 record { +type B1NTT record { int x = 0; string y = ""; }; function testSimpleRecordTypes_1() returns string { - A1 a1 = {}; + A1NTT a1 = {}; any a = a1; - if (a !is A1) { + if (a !is A1NTT) { return "n/a"; } else { return "a is A1"; @@ -112,49 +112,49 @@ function testSimpleRecordTypes_1() returns string { } function testSimpleRecordTypes_2() returns [boolean, boolean] { - B1 b = {}; + B1NTT b = {}; any a = b; - return [a !is A1, a !is B1]; + return [a !is A1NTT, a !is B1NTT]; } -type A2 record { +type A2NTT record { int x = 0; }; -type B2 record { +type B2NTT record { int x = 0; }; function testSimpleRecordTypes_3() returns [boolean, boolean] { - B2 b = {}; + B2NTT b = {}; any a = b; - return [a !is A2, a !is B2]; + return [a !is A2NTT, a !is B2NTT]; } -type Human record { +type HumanNTT record { string name; (function (int, string) returns string) | () foo = (); }; -type Man record { +type ManNTT record { string name; (function (int, string) returns string) | () foo = (); int age = 0; }; function testRecordsWithFunctionType_1() returns [string, string] { - Human m = {name:"Piyal"}; + HumanNTT m = {name:"Piyal"}; any a = m; string s1; string s2; - if (a !is Man) { + if (a !is ManNTT) { s1 = "a is not a man"; } else { s1 = "Man: " + m.name; } - if (a !is Human) { + if (a !is HumanNTT) { s2 = "a is not a human"; } else { s2 = "Human: " + m.name; @@ -164,18 +164,18 @@ function testRecordsWithFunctionType_1() returns [string, string] { } function testRecordsWithFunctionType_2() returns [string, string] { - Man m = {name:"Piyal"}; + ManNTT m = {name:"Piyal"}; any a = m; string s1; string s2; - if (a !is Man) { + if (a !is ManNTT) { s1 = "a is not a man"; } else { s1 = "Man: " + m.name; } - if (a !is Human) { + if (a !is HumanNTT) { s2 = "a is not a human"; } else { s2 = "Human: " + m.name; @@ -184,36 +184,36 @@ function testRecordsWithFunctionType_2() returns [string, string] { return [s1, s2]; } -type X record { +type XTN record { int p = 0; string q = ""; - A1 r = {}; + A1NTT r = {}; }; -type Y record { +type YTN record { int p = 0; string q = ""; - B1 r = {}; // Assignable to A1. Hence Y is assignable to X. + B1NTT r = {}; // Assignable to A1. Hence Y is assignable to X. }; function testNestedRecordTypes() returns [boolean, boolean] { - Y y = {}; + YTN y = {}; any x = y; - return [x is X, x is Y]; + return [x is XTN, x is YTN]; } -type A3 record { +type A3NTT record { int x = 0; }; -type B3 record {| +type B3NTT record {| int x = 0; |}; function testSealedRecordTypes() returns [boolean, boolean] { - A3 a3 = {}; + A3NTT a3 = {}; any a = a3; - return [a !is A3, a !is B3]; + return [a !is A3NTT, a !is B3NTT]; } // ========================== Objects ========================== @@ -375,18 +375,18 @@ function testObjectWithUnorderedFields() returns [string, string, string, string return [s1, s2, s3, s4]; } -public type A4 object { +public type A4NTT object { public int p; public string q; }; -public type B4 object { +public type B4NTT object { public float r; - *A4; + *A4NTT; }; public class C4 { - *B4; + *B4NTT; public boolean s; public function init(int p, string q, float r, boolean s) { @@ -403,11 +403,11 @@ function testPublicObjectEquivalency() returns [string, string, string] { string s2 = "n/a"; string s3 = "n/a"; - if !(x !is A4) { + if !(x !is A4NTT) { s1 = "values: " + x.p.toString() + ", " + x.q; } - if !(x !is B4) { + if !(x !is B4NTT) { s2 = "values: " + x.p.toString() + ", " + x.q + ", " + x.r.toString(); } @@ -418,18 +418,18 @@ function testPublicObjectEquivalency() returns [string, string, string] { return [s1, s2, s3]; } -type A5 object { +type A5NTT object { int p; string q; }; -type B5 object { +type B5NTT object { float r; - *A5; + *A5NTT; }; class C5 { - *B5; + *B5NTT; boolean s; public function init(int p, string q, float r, boolean s) { @@ -446,11 +446,11 @@ function testPrivateObjectEquivalency() returns [string, string, string] { string s2 = "n/a"; string s3 = "n/a"; - if !(x !is A5) { + if !(x !is A5NTT) { s1 = "values: " + x.p.toString() + ", " + x.q; } - if !(x !is B5) { + if !(x !is B5NTT) { s2 = "values: " + x.p.toString() + ", " + x.q + ", " + x.r.toString(); } @@ -467,7 +467,7 @@ function testAnonymousObjectEquivalency() returns [string, string, string] { string s2 = "n/a"; string s3 = "n/a"; - if !(x !is object { public float r; *A4; }) { + if !(x !is object { public float r; *A4NTT; }) { s1 = "values: " + x.p.toString() + ", " + x.q + ", " + x.r.toString(); } @@ -482,50 +482,50 @@ function testAnonymousObjectEquivalency() returns [string, string, string] { return [s1, s2, s3]; } -class Qux { - Qux? fn; +class QuxNeg { + QuxNeg? fn; - public function init(Qux? fn = ()) { + public function init(QuxNeg? fn = ()) { self.fn = fn; } } -class Quux { - Quux? fn = (); +class QuuxNeg { + QuuxNeg? fn = (); } -class Quuz { - Quuz? fn = (); +class QuuzNeg { + QuuzNeg? fn = (); int i = 1; } -class ABC { - Qux f; +class ABCNeg { + QuxNeg f; string s; - function init(Qux f, string s) { + function init(QuxNeg f, string s) { self.f = f; self.s = s; } } function testObjectIsCheckWithCycles() { - Qux f1 = new; - Qux f2 = new (f1); + QuxNeg f1 = new; + QuxNeg f2 = new (f1); - any a1 = f1; - assertFalse(a1 !is Quux); - assertTrue(a1 !is Quuz); + any a1 = f1; + assertFalse(a1 !is QuuxNeg); + assertTrue(a1 !is QuuzNeg); - any a2 = f2; - assertFalse(a2 !is Quux); - assertTrue(a2 !is Quuz); + any a2 = f2; + assertFalse(a2 !is QuuxNeg); + assertTrue(a2 !is QuuzNeg); - ABC ob = new (f2, "ballerina"); + ABCNeg ob = new (f2, "ballerina"); any a3 = ob; - assertFalse(a3 !is object { Qux f; }); - assertTrue(a3 !is object { Quuz f; }); + assertFalse(a3 !is object {QuxNeg f;}); + assertTrue(a3 !is object {QuuzNeg f;}); } // ========================== Arrays ========================== @@ -539,11 +539,11 @@ function testSimpleArrays() returns [boolean, boolean, boolean, boolean, boolean } function testRecordArrays() returns [boolean, boolean, boolean, boolean] { - X[] a = [{}, {}]; - X[][] b = [[{}, {}], [{}, {}]]; + XTN[] a = [{}, {}]; + XTN[][] b = [[{}, {}], [{}, {}]]; any c = a; any d = b; - return [c !is X[], d !is X[][], c !is Y[], d !is Y[][]]; + return [c !is XTN[], d !is XTN[][], c !is YTN[], d !is YTN[][]]; } public function testUnionType() { @@ -641,20 +641,20 @@ function testSimpleTuples() returns [boolean, boolean, boolean, boolean, boolean } function testTupleWithAssignableTypes_1() returns [boolean, boolean, boolean, boolean] { - [X, Y] p = [{}, {}]; + [XTN, YTN] p = [{}, {}]; any q = p; - boolean b0 = q !is [X, X]; - boolean b1 = q !is [X, Y]; - boolean b2 = q !is [Y, X]; - boolean b3 = q !is [Y, Y]; + boolean b0 = q !is [XTN, XTN]; + boolean b1 = q !is [XTN, YTN]; + boolean b2 = q !is [YTN, XTN]; + boolean b3 = q !is [YTN, YTN]; return [b0, b1, b2, b3]; } function testTupleWithAssignableTypes_2() returns boolean { - [Y, Y] p = [{}, {}]; - [X, Y] q = p; - boolean b1 = q !is [Y, Y]; - return q !is [Y, Y]; + [YTN, YTN] p = [{}, {}]; + [XTN, YTN] q = p; + boolean b1 = q !is [YTN, YTN]; + return q !is [YTN, YTN]; } public function testRestType() { @@ -1079,12 +1079,12 @@ public function testXMLNeverType() { xml e = xml ``; assertEquality( e !is byte, true); - assertEquality( e !is xml<'xml:Element>, true); + assertEquality( e is xml<'xml:Element>, true); assertEquality( e !is xml<'xml:Text>, false); assertEquality( e !is xml, false); assertEquality( e !is 'xml:Text, false); assertEquality( e !is 'xml:Element, true); - assertEquality( e !is xml<'xml:Element|'xml:Comment>, true); + assertEquality( e is xml<'xml:Element|'xml:Comment>, true); } function testXMLTextType(){ diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/type-test-expr.bal b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/type-test-expr.bal index 8073e3b11f14..3a8c6533ec95 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/type-test-expr.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/type-test-expr.bal @@ -100,21 +100,21 @@ function testTypeCheckInTernary() returns string { // ========================== Records ========================== -type A1 record { +type A1TN record { int x = 0; }; -type B1 record { +type B1TN record { int x = 0; string y = ""; }; function testSimpleRecordTypes_1() returns string { - A1 a1 = {}; + A1TN a1 = {}; any a = a1; - if (a is A1) { + if (a is A1TN) { return "a is A1"; - } else if (a is B1) { + } else if (a is B1TN) { return "a is B1"; } @@ -122,49 +122,49 @@ function testSimpleRecordTypes_1() returns string { } function testSimpleRecordTypes_2() returns [boolean, boolean] { - B1 b = {}; + B1TN b = {}; any a = b; - return [a is A1, a is B1]; + return [a is A1TN, a is B1TN]; } -type A2 record { +type A2TN record { int x = 0; }; -type B2 record { +type B2TN record { int x = 0; }; function testSimpleRecordTypes_3() returns [boolean, boolean] { - B2 b = {}; + B2TN b = {}; any a = b; - return [a is A2, a is B2]; + return [a is A2TN, a is B2TN]; } -type Human record { +type HumanTN record { string name; (function (int, string) returns string) | () foo = (); }; -type Man record { +type ManTN record { string name; (function (int, string) returns string) | () foo = (); int age = 0; }; function testRecordsWithFunctionType_1() returns [string, string] { - Human m = {name:"Piyal"}; + HumanTN m = {name:"Piyal"}; any a = m; string s1; string s2; - if (a is Man) { + if (a is ManTN) { s1 = "Man: " + m.name; } else { s1 = "a is not a man"; } - if (a is Human) { + if (a is HumanTN) { s2 = "Human: " + m.name; } else { s2 = "a is not a human"; @@ -174,18 +174,18 @@ function testRecordsWithFunctionType_1() returns [string, string] { } function testRecordsWithFunctionType_2() returns [string, string] { - Man m = {name:"Piyal"}; + ManTN m = {name:"Piyal"}; any a = m; string s1; string s2; - if (a is Man) { + if (a is ManTN) { s1 = "Man: " + m.name; } else { s1 = "a is not a man"; } - if (a is Human) { + if (a is HumanTN) { s2 = "Human: " + m.name; } else { s2 = "a is not a human"; @@ -194,39 +194,39 @@ function testRecordsWithFunctionType_2() returns [string, string] { return [s1, s2]; } -type X record { +type XTTE record { int p = 0; string q = ""; - A1 r = {}; + A1TN r = {}; }; -type Y record { +type YTTE record { int p = 0; string q = ""; - B1 r = {}; // Assignable to A1. Hence Y is assignable to X. + B1TN r = {}; // Assignable to A1. Hence Y is assignable to X. }; function testNestedRecordTypes() returns [boolean, boolean] { - Y y = {}; + YTTE y = {}; any x = y; - return [x is X, x is Y]; + return [x is XTTE, x is YTTE]; } -type A3 record { +type A3TN record { int x = 0; }; -type B3 record {| +type B3TN record {| int x = 0; |}; function testSealedRecordTypes() returns [boolean, boolean] { - A3 a3 = {}; + A3TN a3 = {}; any a = a3; - return [a is A3, a is B3]; + return [a is A3TN, a is B3TN]; } -type Country record {| +type CountryTN record {| readonly string code?; string name?; record {| @@ -236,7 +236,7 @@ type Country record {| |} continent?; |}; -type MyCountry record {| +type MyCountryTN record {| readonly string code?; record {| string code?; @@ -245,9 +245,9 @@ type MyCountry record {| |}; function testRecordsWithOptionalFields() { - MyCountry x = {}; - Country y = x; - test:assertTrue(x is Country); + MyCountryTN x = {}; + CountryTN y = x; + test:assertTrue(x is CountryTN); } // ========================== Objects ========================== @@ -409,18 +409,18 @@ function testObjectWithUnorderedFields() returns [string, string, string, string return [s1, s2, s3, s4]; } -public type A4 object { +public type A4TN object { public int p; public string q; }; -public type B4 object { +public type B4TN object { public float r; - *A4; + *A4TN; }; -public class C4 { - *B4; +public class C4TN { + *B4TN; public boolean s; public function init(int p, string q, float r, boolean s) { @@ -432,16 +432,16 @@ public class C4 { } function testPublicObjectEquivalency() returns [string, string, string] { - any x = new C4(5, "foo", 6.7, true); + any x = new C4TN(5, "foo", 6.7, true); string s1 = "n/a"; string s2 = "n/a"; string s3 = "n/a"; - if(x is A4) { + if(x is A4TN) { s1 = "values: " + x.p.toString() + ", " + x.q; } - if (x is B4) { + if (x is B4TN) { s2 = "values: " + x.p.toString() + ", " + x.q + ", " + x.r.toString(); } @@ -452,18 +452,18 @@ function testPublicObjectEquivalency() returns [string, string, string] { return [s1, s2, s3]; } -type A5 object { +type A5TN object { int p; string q; }; -type B5 object { +type B5TN object { float r; - *A5; + *A5TN; }; class C5 { - *B5; + *B5TN; boolean s; public function init(int p, string q, float r, boolean s) { @@ -480,11 +480,11 @@ function testPrivateObjectEquivalency() returns [string, string, string] { string s2 = "n/a"; string s3 = "n/a"; - if(x is A5) { + if(x is A5TN) { s1 = "values: " + x.p.toString() + ", " + x.q; } - if (x is B5) { + if (x is B5TN) { s2 = "values: " + x.p.toString() + ", " + x.q + ", " + x.r.toString(); } @@ -496,12 +496,12 @@ function testPrivateObjectEquivalency() returns [string, string, string] { } function testAnonymousObjectEquivalency() returns [string, string, string] { - any x = new C4(5, "foo", 6.7, true); + any x = new C4TN(5, "foo", 6.7, true); string s1 = "n/a"; string s2 = "n/a"; string s3 = "n/a"; - if(x is object { public float r; *A4; }) { + if(x is object { public float r; *A4TN; }) { s1 = "values: " + x.p.toString() + ", " + x.q + ", " + x.r.toString(); } @@ -516,50 +516,50 @@ function testAnonymousObjectEquivalency() returns [string, string, string] { return [s1, s2, s3]; } -class Qux { - Qux? fn; +class QuxFoo { + QuxFoo? fn; - public function init(Qux? fn = ()) { + public function init(QuxFoo? fn = ()) { self.fn = fn; } } -class Quux { - Quux? fn = (); +class QuuxFoo { + QuuxFoo? fn = (); } -class Quuz { - Quuz? fn = (); +class QuuzFoo { + QuuzFoo? fn = (); int i = 1; } -class ABC { - Qux f; +class ABCFoo { + QuxFoo f; string s; - function init(Qux f, string s) { + function init(QuxFoo f, string s) { self.f = f; self.s = s; } } function testObjectIsCheckWithCycles() { - Qux f1 = new; - Qux f2 = new (f1); + QuxFoo f1 = new; + QuxFoo f2 = new (f1); - any a1 = f1; - test:assertTrue(a1 is Quux); - test:assertFalse(a1 is Quuz); + any a1 = f1; + test:assertTrue(a1 is QuuxFoo); + test:assertFalse(a1 is QuuzFoo); - any a2 = f2; - test:assertTrue(a2 is Quux); - test:assertFalse(a2 is Quuz); + any a2 = f2; + test:assertTrue(a2 is QuuxFoo); + test:assertFalse(a2 is QuuzFoo); - ABC ob = new (f2, "ballerina"); + ABCFoo ob = new (f2, "ballerina"); any a3 = ob; - test:assertTrue(a3 is object { Qux f; }); - test:assertFalse(a3 is object { Quuz f; }); + test:assertTrue(a3 is object {QuxFoo f;}); + test:assertFalse(a3 is object {QuuzFoo f;}); } service class ServiceClassA { @@ -625,11 +625,11 @@ function testSimpleArrays() returns [boolean, boolean, boolean, boolean, boolean } function testRecordArrays() returns [boolean, boolean, boolean, boolean] { - X[] a = [{}, {}]; - X[][] b = [[{}, {}], [{}, {}]]; + XTTE[] a = [{}, {}]; + XTTE[][] b = [[{}, {}], [{}, {}]]; any c = a; any d = b; - return [c is X[], d is X[][], c is Y[], d is Y[][]]; + return [c is XTTE[], d is XTTE[][], c is YTTE[], d is YTTE[][]]; } public function testUnionType() { @@ -742,20 +742,20 @@ function testSimpleTuples() returns [boolean, boolean, boolean, boolean, boolean } function testTupleWithAssignableTypes_1() returns [boolean, boolean, boolean, boolean] { - [X, Y] p = [{}, {}]; + [XTTE, YTTE] p = [{}, {}]; any q = p; - boolean b0 = q is [X, X]; - boolean b1 = q is [X, Y]; - boolean b2 = q is [Y, X]; - boolean b3 = q is [Y, Y]; + boolean b0 = q is [XTTE, XTTE]; + boolean b1 = q is [XTTE, YTTE]; + boolean b2 = q is [YTTE, XTTE]; + boolean b3 = q is [YTTE, YTTE]; return [b0, b1, b2, b3]; } function testTupleWithAssignableTypes_2() returns boolean { - [Y, Y] p = [{}, {}]; - [X, Y] q = p; - boolean b1 = q is [Y, Y]; - return q is [Y, Y]; + [YTTE, YTTE] p = [{}, {}]; + [XTTE, YTTE] q = p; + boolean b1 = q is [YTTE, YTTE]; + return q is [YTTE, YTTE]; } public function testRestType() { @@ -852,35 +852,35 @@ function testJsonArrays() returns [boolean, boolean, boolean] { // ========================== Finite type ========================== -type State "on"|"off"; +type StateTN "on"|"off"; function testFiniteType() returns [boolean, boolean, boolean] { - State a = "on"; + StateTN a = "on"; any b = a; any c = "off"; any d = "hello"; - return [b is State, c is State, d is State]; + return [b is StateTN, c is StateTN, d is StateTN]; } function testFiniteTypeInTuple() returns [boolean, boolean, boolean, boolean] { - [State, string] x = ["on", "off"]; + [StateTN, string] x = ["on", "off"]; any y = x; - boolean b0 = y is [State, State]; - boolean b1 = y is [State, string]; - boolean b2 = y is [string, State]; + boolean b0 = y is [StateTN, StateTN]; + boolean b1 = y is [StateTN, string]; + boolean b2 = y is [string, StateTN]; boolean b3 = y is [string, string]; return [b0, b1, b2, b3]; } -function testFiniteTypeInTuplePoisoning() returns [State, State] { - [State, string] x = ["on", "off"]; +function testFiniteTypeInTuplePoisoning() returns [StateTN, StateTN] { + [StateTN, string] x = ["on", "off"]; any y = x; - [State, State] z = ["on", "on"]; + [StateTN, StateTN] z = ["on", "on"]; - if (y is [State, State]) { + if (y is [StateTN, StateTN]) { z = y; } @@ -892,11 +892,11 @@ public const APPLE = "apple"; public const ORANGE = "orange"; public const GRAPE = "grape"; -type Fruit APPLE | ORANGE | GRAPE; +type FruitTN APPLE | ORANGE | GRAPE; function testFiniteType_1() returns string { any a = APPLE; - if (a is Fruit) { + if (a is FruitTN) { return "a is a fruit"; } @@ -1001,13 +1001,13 @@ function testIntersectingUnionFalse() returns [boolean, boolean] { function testValueTypeAsFiniteTypeTrue() returns [boolean, boolean] { string s = "orange"; float f = 2.0; - return [s is Fruit, f is IntTwo]; + return [s is FruitTN, f is IntTwo]; } function testValueTypeAsFiniteTypeFalse() returns [boolean, boolean] { string s = "mango"; float f = 12.0; - return [s is Fruit, f is IntTwo]; + return [s is FruitTN, f is IntTwo]; } const ERR_REASON = "error reason"; @@ -1213,12 +1213,12 @@ public function testXMLNeverType() { xml e = xml ``; test:assertEquals( e is byte, false); - test:assertEquals( e is xml<'xml:Element>, false); + test:assertEquals( e is xml<'xml:Element>, true); test:assertEquals( e is xml<'xml:Text>, true); test:assertEquals( e is xml, true); test:assertEquals( e is 'xml:Text, true); test:assertEquals( e is 'xml:Element, false); - test:assertEquals( e is xml<'xml:Element|'xml:Comment>, false); + test:assertEquals( e is xml<'xml:Element|'xml:Comment>, true); } function testXMLTextType(){ diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/listconstructor/list_constructor_infer_type.bal b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/listconstructor/list_constructor_infer_type.bal index 8a4be1b310b8..2001b8768bc0 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/listconstructor/list_constructor_infer_type.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/listconstructor/list_constructor_infer_type.bal @@ -14,12 +14,12 @@ // specific language governing permissions and limitations // under the License. -type Foo record { +type FooListInfer record { string s; int i = 1; }; -class Bar { +class BarListInfer { int i; public function init(int i) { @@ -27,8 +27,8 @@ class Bar { } } -Foo f = {s: "hello"}; -Bar b = new (1); +FooListInfer f = {s: "hello"}; +BarListInfer b = new (1); json j = 1; function inferSimpleTuple() { @@ -40,14 +40,14 @@ function inferSimpleTuple() { function inferStructuredTuple() { var x = [f, b, xml `text`, j]; typedesc ta = typeof x; - assertEquality("typedesc [Foo,Bar,lang.xml:Text,json]", ta.toString()); + assertEquality("typedesc [FooListInfer,BarListInfer,lang.xml:Text,json]", ta.toString()); } function inferNestedTuple() { int[2] arr = [1, 2]; var x = [1, 2.0d, [3, f, [b, b]], arr, j]; typedesc ta = typeof x; - assertEquality("typedesc [int,decimal,[int,Foo,[Bar,Bar]],int[2],json]", ta.toString()); + assertEquality("typedesc [int,decimal,[int,FooListInfer,[BarListInfer,BarListInfer]],int[2],json]", ta.toString()); } function testInferSameRecordsInTuple() { @@ -102,15 +102,15 @@ function testInferringForReadOnly() { error err = res; assertEquality("modification not allowed on readonly value", err.detail()["message"]); - Foo & readonly foo = { + FooListInfer & readonly foo = { s: "May", i: 20 }; readonly rd2 = [1, [b, false], foo, foo]; - assertEquality(true, rd2 is [int, [boolean, boolean], Foo, Foo & readonly] & readonly); - assertEquality(false, rd2 is [int, [boolean, boolean], object {} & readonly, Foo & readonly] & readonly); - [int, [boolean, boolean], Foo, Foo] arr2 = <[int, [boolean, boolean], Foo, Foo] & readonly> checkpanic rd2; + assertEquality(true, rd2 is [int, [boolean, boolean], FooListInfer, FooListInfer & readonly] & readonly); + assertEquality(false, rd2 is [int, [boolean, boolean], object {} & readonly, FooListInfer & readonly] & readonly); + [int, [boolean, boolean], FooListInfer, FooListInfer] arr2 = <[int, [boolean, boolean], FooListInfer, FooListInfer] & readonly> checkpanic rd2; fn = function() { arr2[0] = 2; @@ -123,17 +123,17 @@ function testInferringForReadOnly() { } function testInferringForReadOnlyInUnion() { - Foo & readonly foo = { + FooListInfer & readonly foo = { s: "May", i: 20 }; boolean b = true; - readonly|(Foo|int)[] rd = [1, [b, false], foo, foo]; + readonly|(FooListInfer|int)[] rd = [1, [b, false], foo, foo]; - assertEquality(true, rd is [int, [boolean, boolean], Foo, Foo & readonly] & readonly); - assertEquality(false, rd is [int, [boolean, boolean], object {} & readonly, Foo & readonly] & readonly); - [int, [boolean, boolean], Foo, Foo] arr = <[int, [boolean, boolean], Foo, Foo] & readonly> checkpanic rd; + assertEquality(true, rd is [int, [boolean, boolean], FooListInfer, FooListInfer & readonly] & readonly); + assertEquality(false, rd is [int, [boolean, boolean], object {} & readonly, FooListInfer & readonly] & readonly); + [int, [boolean, boolean], FooListInfer, FooListInfer] arr = <[int, [boolean, boolean], FooListInfer, FooListInfer] & readonly> checkpanic rd; var fn = function() { arr[0] = 2; 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..eb260a6c4ec9 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 @@ -481,27 +481,27 @@ function testStringToJson(string s) returns (json) { return s; } -type Person record { +type PersonTC record { string name; int age; map address = {}; int[] marks = []; - Person | () parent = (); + PersonTC | () parent = (); json info = {}; anydata a = 0; float score = 0.0; boolean alive = true; }; -type Student record { +type StudentTC record { string name; int age; map address = {}; int[] marks = []; }; -function testStructToStruct() returns (Student) { - Person p = { name:"Supun", +function testStructToStruct() returns (StudentTC) { + PersonTC p = { name:"Supun", age:25, parent:{name:"Parent", age:50}, address:{"city":"Kandy", "country":"SriLanka"}, @@ -512,13 +512,13 @@ function testStructToStruct() returns (Student) { return p2; } -function testNullStructToStruct() returns Student { - Person? p = (); - return p; +function testNullStructToStruct() returns StudentTC { + PersonTC? p = (); + return p; } -function testStructAsAnyToStruct() returns Person|error { - Person p1 = { name:"Supun", +function testStructAsAnyToStruct() returns PersonTC|error { + PersonTC p1 = { name:"Supun", age:25, parent:{name:"Parent", age:50}, address:{"city":"Kandy", "country":"SriLanka"}, @@ -526,11 +526,11 @@ function testStructAsAnyToStruct() returns Person|error { marks:[24, 81] }; any a = p1; - var p2 = check trap a; + var p2 = check trap a; return p2; } -function testAnyToStruct() returns Person { +function testAnyToStruct() returns PersonTC { json address = {"city":"Kandy", "country":"SriLanka"}; map parent = {name:"Parent", age:50}; map info = {status:"single"}; @@ -543,18 +543,18 @@ function testAnyToStruct() returns Person { marks:marks }; any b = a; - var p2 = b; + var p2 = b; return p2; } -function testAnyNullToStruct() returns Person { +function testAnyNullToStruct() returns PersonTC { any a = (); - var p = a; + var p = a; return p; } function testRecordToAny() returns (any) { - Person p = { name:"Supun", + PersonTC p = { name:"Supun", age:25, parent:{name:"Parent", age:50}, address:{"city":"Kandy", "country":"SriLanka"}, @@ -601,7 +601,7 @@ function testBooleanInJsonToInt() { } } -type Address record { +type AddressTC record { string city; string country = ""; }; @@ -671,7 +671,7 @@ function testAnyMapToJson() returns json { } function testAnyStructToJson() returns json { - Address adrs = {city:"CA"}; + AddressTC adrs = {city:"CA"}; any a = adrs; json value; value = a; @@ -917,18 +917,18 @@ function testJSONValueCasting() returns [string|error, int|error, float|error, b } function testAnyToTable() { - table tb = table [ + table tb = table [ {id:1, name:"Jane"}, {id:2, name:"Anne"} ]; any anyValue = tb; - var casted = > anyValue; - table castedValue = casted; + var casted = > anyValue; + table castedValue = casted; assertEquality("[{\"id\":1,\"name\":\"Jane\"},{\"id\":2,\"name\":\"Anne\"}]", castedValue.toString()); } -type Employee record { +type EmployeeTC record { int id; string name; }; @@ -998,32 +998,31 @@ function testCastOfReadonlyUnionArrayToByteArray() { assertEquality("[1,2,3]", f.toString()); } -type Foo record {| +type FooTC record {| string s; int[] arr; |}; -type Bar record {| +type BarTC record {| string s; byte[] arr; |}; function testCastOfReadonlyRecord() { - (Foo & readonly) f = {s: "a", arr: [1,2,3]}; + (FooTC & readonly) f = {s: "a", arr: [1,2,3]}; any a = f; - Bar b = a; + BarTC b = a; assertEquality(true, b === a); assertEquality("{\"s\":\"a\",\"arr\":[1,2,3]}", b.toString()); } function testCastOfReadonlyRecordNegative() { - (Foo & readonly) f = {s: "a", arr: [1,2,300]}; + (FooTC & readonly) f = {s: "a", arr: [1,2,300]}; any a = f; - Bar|error b = trap a; + BarTC|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: '(FooTC & readonly)' cannot be cast to 'BarTC'"; assertEquality("{ballerina}TypeCastError", err.message()); assertEquality(errMsg, checkpanic err.detail()["message"]); } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/dependently_typed_functions_bir_test.bal b/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/dependently_typed_functions_bir_test.bal index 7c9f597e4e39..28bd92404743 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/dependently_typed_functions_bir_test.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/dependently_typed_functions_bir_test.bal @@ -16,18 +16,17 @@ import testorg/foo.dependently_typed as rt; -type Person record { +type PersonDTBT record { readonly string name; int age; }; -type Employee record { - *Person; +type EmployeeDTBT record { + *PersonDTBT; string designation; }; -Person expPerson = {name: "John Doe", age: 20}; - +PersonDTBT expPerson = {name: "John Doe", age: 20}; // Test functions @@ -49,11 +48,11 @@ function testSimpleTypes() { } function testRecordVarRef() { - Person p = rt:getRecord(); + PersonDTBT p = rt:getRecord(); assert(expPerson, p); - Employee e = rt:getRecord(td = Employee); - assert({name: "Jane Doe", age: 25, designation: "Software Engineer"}, e); + EmployeeDTBT e = rt:getRecord(td = EmployeeDTBT); + assert({name: "Jane Doe", age: 25, designation: "Software Engineer"}, e); } function testVarRefInMapConstraint() { @@ -69,12 +68,12 @@ function testRuntimeCastError() { } function testVarRefUseInMultiplePlaces() { - [int, Person, float] tup1 = rt:getTuple(int, Person); - assert(<[int, Person, float]>[150, expPerson, 12.34], tup1); + [int, PersonDTBT, float] tup1 = rt:getTuple(int, PersonDTBT); + assert(<[int, PersonDTBT, float]>[150, expPerson, 12.34], tup1); } function testUnionTypes() { - int|Person u = rt:getVariedUnion(1, int, Person); + int|PersonDTBT u = rt:getVariedUnion(1, int, PersonDTBT); assert(expPerson, u); } @@ -84,7 +83,7 @@ function testArrayTypes() { } function testCastingForInvalidValues() { - int _ = rt:getInvalidValue(int, Person); + int _ = rt:getInvalidValue(int, PersonDTBT); } type XmlElement xml:Element; @@ -101,19 +100,21 @@ function testStream() { stream newSt = rt:getStream(st, string); string s = ""; - newSt.forEach(function (string x) { s += x; }); + newSt.forEach(function(string x) { + s += x; + }); assert("helloworldfromballerina", s); } function testTable() { - table key(name) tab = table [ - { name: "Chiran", age: 33, designation: "SE" }, - { name: "Mohan", age: 37, designation: "SE" }, - { name: "Gima", age: 38, designation: "SE" }, - { name: "Granier", age: 34, designation: "SE" } + table key(name) tab = table [ + {name: "Chiran", age: 33, designation: "SE"}, + {name: "Mohan", age: 37, designation: "SE"}, + {name: "Gima", age: 38, designation: "SE"}, + {name: "Granier", age: 34, designation: "SE"} ]; - table newTab = rt:getTable(tab, Person); + table newTab = rt:getTable(tab, PersonDTBT); assert(tab, newTab); } @@ -125,12 +126,12 @@ function testFunctionPointers() { } function testTypedesc() { - typedesc tP = rt:getTypedesc(Person); - assert(Person.toString(), tP.toString()); + typedesc tP = rt:getTypedesc(PersonDTBT); + assert(PersonDTBT.toString(), tP.toString()); } function testFuture() { - var fn = function (string name) returns string => "Hello " + name; + var fn = function(string name) returns string => "Hello " + name; future f = start fn("Pubudu"); future fNew = rt:getFuture(f, string); string res = checkpanic wait fNew; @@ -150,9 +151,12 @@ class PersonObj { function name() returns string => self.fname + " " + self.lname; } -type PersonTable table; +type PersonTable table; + type IntStream stream; + type IntArray int[]; + type XmlType xml; function testComplexTypes() { @@ -165,7 +169,7 @@ function testComplexTypes() { int[] ar = rt:echo([20, 30, 40], IntArray); assert([20, 30, 40], ar); - PersonObj pObj = new("John", "Doe"); + PersonObj pObj = new ("John", "Doe"); PersonObj nObj = rt:echo(pObj, PersonObj); assertSame(pObj, nObj); @@ -174,17 +178,19 @@ function testComplexTypes() { stream newSt = rt:echo(st, IntStream); int tot = 0; - newSt.forEach(function (int x1) { tot+= x1; }); + newSt.forEach(function(int x1) { + tot += x1; + }); assert(150, tot); - table key(name) tab = table [ - { name: "Chiran", age: 33}, - { name: "Mohan", age: 37}, - { name: "Gima", age: 38}, - { name: "Granier", age: 34} + table key(name) tab = table [ + {name: "Chiran", age: 33}, + {name: "Mohan", age: 37}, + {name: "Gima", age: 38}, + {name: "Granier", age: 34} ]; - table newTab = rt:echo(tab, PersonTable); + table newTab = rt:echo(tab, PersonTable); assert(tab, newTab); } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/dependently_typed_functions_test.bal b/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/dependently_typed_functions_test.bal index 49817c076771..bb681bdbc8c4 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/dependently_typed_functions_test.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/dependently_typed_functions_test.bal @@ -17,23 +17,24 @@ import ballerina/jballerina.java; type ItemType xml:Element|xml:Comment|xml:ProcessingInstruction|xml:Text; + type XmlElement xml:Element; -type Person record { +type PersonDTFT record { readonly string name; int age; }; -type Employee record { - *Person; +type EmployeeDTFT record { + *PersonDTFT; string designation; }; -Person expPerson = {name: "John Doe", age: 20}; +PersonDTFT expPerson = {name: "John Doe", age: 20}; -type Foo int|float; +type FooDTFT int|float; -type Foo2 Foo; +type Foo2DTFT FooDTFT; type AnnotationRecord record {| int minValue; @@ -52,7 +53,7 @@ type Foo3 int|float; minValue: 12, maxValue: 24 } -type Foo4 Foo; +type Foo4 FooDTFT; // Test functions @@ -74,31 +75,31 @@ function testSimpleTypes() { } function testReferredTypes() { - map annotationMapValue = getAnnotationValue(Foo); - AnnotationRecord? 'annotation = annotationMapValue["BarAnnotation"]; + map annotationMapValue = getAnnotationValue(FooDTFT); + AnnotationRecord? 'annotation = annotationMapValue["BarAnnotation"]; assert('annotation, ()); - map annotationMapValue2 = getAnnotationValue(Foo2); - AnnotationRecord? annotation2 = annotationMapValue2["BarAnnotation"]; + map annotationMapValue2 = getAnnotationValue(Foo2DTFT); + AnnotationRecord? annotation2 = annotationMapValue2["BarAnnotation"]; assert(annotation2, ()); map annotationMapValue3 = getAnnotationValue(Foo3); - AnnotationRecord annotation3 = annotationMapValue3["BarAnnotation"]; + AnnotationRecord annotation3 = annotationMapValue3["BarAnnotation"]; assert(annotation3, {minValue: 18, maxValue: 36}); assertTrue(getAnnotationValue2(1, Foo3, "BarAnnotation", 18, 36) is anydata); map annotationMapValue4 = getAnnotationValue(Foo4); - AnnotationRecord annotation4 = annotationMapValue4["BarAnnotation"]; - assert(annotation4, {minValue: 12, maxValue: 24}); + AnnotationRecord annotation4 = annotationMapValue4["BarAnnotation"]; + assert(annotation4, {minValue: 12, maxValue: 24}); assertTrue(getAnnotationValue2(1, Foo4, "BarAnnotation", 12, 24) is anydata); } function testRecordVarRef() { - Person p = getRecord(); + PersonDTFT p = getRecord(); assert(expPerson, p); - Employee e = getRecord(td = Employee); - assert({name: "Jane Doe", age: 25, designation: "Software Engineer"}, e); + EmployeeDTFT e = getRecord(td = EmployeeDTFT); + assert({name: "Jane Doe", age: 25, designation: "Software Engineer"}, e); } function testVarRefInMapConstraint() { @@ -114,12 +115,12 @@ function testRuntimeCastError() { } function testVarRefUseInMultiplePlaces() { - [int, Person, float] tup1 = getTuple(int, Person); - assert(<[int, Person, float]>[150, expPerson, 12.34], tup1); + [int, PersonDTFT, float] tup1 = getTuple(int, PersonDTFT); + assert(<[int, PersonDTFT, float]>[150, expPerson, 12.34], tup1); } function testUnionTypes() { - int|Person u = getVariedUnion(1, int, Person); + int|PersonDTFT u = getVariedUnion(1, int, PersonDTFT); assert(expPerson, u); } @@ -129,7 +130,7 @@ function testArrayTypes() { } function testCastingForInvalidValues() { - int x = getInvalidValue(int, Person); + int x = getInvalidValue(int, PersonDTFT); } function testXML() { @@ -144,19 +145,21 @@ function testStream() { stream newSt = getStream(st, string); string s = ""; - error? err = newSt.forEach(function (string x) { s += x; }); + error? err = newSt.forEach(function(string x) { + s += x; + }); assert("helloworldfromballerina", s); } function testTable() { - table key(name) tab = table [ - { name: "Chiran", age: 33, designation: "SE" }, - { name: "Mohan", age: 37, designation: "SE" }, - { name: "Gima", age: 38, designation: "SE" }, - { name: "Granier", age: 34, designation: "SE" } + table key(name) tab = table [ + {name: "Chiran", age: 33, designation: "SE"}, + {name: "Mohan", age: 37, designation: "SE"}, + {name: "Gima", age: 38, designation: "SE"}, + {name: "Granier", age: 34, designation: "SE"} ]; - table newTab = getTable(tab, Person); + table newTab = getTable(tab, PersonDTFT); assert(tab, newTab); } @@ -168,12 +171,12 @@ function testFunctionPointers() { } function testTypedesc() { - typedesc tP = getTypedesc(Person); - assert(Person.toString(), tP.toString()); + typedesc tP = getTypedesc(PersonDTFT); + assert(PersonDTFT.toString(), tP.toString()); } function testFuture() { - var fn = function (string name) returns string => "Hello " + name; + var fn = function(string name) returns string => "Hello " + name; future f = start fn("Pubudu"); future fNew = getFuture(f, string); string res = checkpanic wait fNew; @@ -211,7 +214,7 @@ class PersonObj { type IntStream stream; -type PersonTable table; +type PersonTable table; type IntArray int[]; @@ -227,7 +230,7 @@ function testComplexTypes() { int[] ar = echo([20, 30, 40], IntArray); assert([20, 30, 40], ar); - PersonObj pObj = new("John", "Doe"); + PersonObj pObj = new ("John", "Doe"); PersonObj nObj = echo(pObj, PersonObj); assertSame(pObj, nObj); @@ -236,22 +239,24 @@ function testComplexTypes() { stream newSt = echo(st, IntStream); int tot = 0; - error? err = newSt.forEach(function (int x1) { tot+= x1; }); + error? err = newSt.forEach(function(int x1) { + tot += x1; + }); assert(150, tot); - table key(name) tab = table [ - { name: "Chiran", age: 33}, - { name: "Mohan", age: 37}, - { name: "Gima", age: 38}, - { name: "Granier", age: 34} + table key(name) tab = table [ + {name: "Chiran", age: 33}, + {name: "Mohan", age: 37}, + {name: "Gima", age: 38}, + {name: "Granier", age: 34} ]; - table newTab = echo(tab, PersonTable); + table newTab = echo(tab, PersonTable); assert(tab, newTab); } function testObjectExternFunctions() { - PersonObj pObj = new("John", "Doe"); + PersonObj pObj = new ("John", "Doe"); string s = pObj.getObjectValue(string); assert("John Doe", s); s = pObj.getObjectValueWithTypeDescParam(string); @@ -275,7 +280,6 @@ function testFunctionAssignment() { x = fn(string); } - // Interop functions function getValue(typedesc td) returns td = @java:Method { 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", @@ -283,20 +287,20 @@ function getValue(typedesc td) returns td = @j paramTypes: ["io.ballerina.runtime.api.values.BTypedesc"] } external; -function getAnnotationValue(typedesc y) returns map = +function getAnnotationValue(typedesc y) returns map = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "getAnnotationValue" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "getAnnotationValue" +} external; function getAnnotationValue2(anydata value, typedesc td = <>, string annotationName = "", - int min = 0, int max = 0) returns td|error = + int min = 0, int max = 0) returns td|error = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "getAnnotationValue2" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "getAnnotationValue2" +} external; -function getRecord(typedesc td = Person) returns td = @java:Method { +function getRecord(typedesc td = PersonDTFT) returns td = @java:Method { 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", name: "getRecord", paramTypes: ["io.ballerina.runtime.api.values.BTypedesc"] @@ -314,7 +318,7 @@ function getTuple(typedesc td1, typedesc td2, typedesc td1, typedesc td2) returns (td1|td2) = @java:Method { +function getVariedUnion(int x, typedesc td1, typedesc td2) returns (td1|td2) = @java:Method { 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", name: "getVariedUnion", paramTypes: ["long", "io.ballerina.runtime.api.values.BTypedesc", "io.ballerina.runtime.api.values.BTypedesc"] @@ -332,7 +336,7 @@ function getArrayInferred(typedesc td = <>) returns td[] = @java:Method paramTypes: ["io.ballerina.runtime.api.values.BTypedesc"] } external; -function getInvalidValue(typedesc td1, typedesc td2) returns td1 = @java:Method { +function getInvalidValue(typedesc td1, typedesc td2) returns td1 = @java:Method { 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", name: "getInvalidValue", paramTypes: ["io.ballerina.runtime.api.values.BTypedesc", "io.ballerina.runtime.api.values.BTypedesc"] @@ -360,8 +364,11 @@ function getFunction(function (string|int) returns anydata fn, typedesc returns function (param) returns ret = @java:Method { 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", name: "getFunction", - paramTypes: ["io.ballerina.runtime.api.values.BFunctionPointer", "io.ballerina.runtime.api.values.BTypedesc", - "io.ballerina.runtime.api.values.BTypedesc"] + paramTypes: [ + "io.ballerina.runtime.api.values.BFunctionPointer", + "io.ballerina.runtime.api.values.BTypedesc", + "io.ballerina.runtime.api.values.BTypedesc" + ] } external; function getTypedesc(typedesc td) returns typedesc = @java:Method { @@ -425,13 +432,13 @@ var outParameterObject = object OutParameter { function testDependentlyTypedMethodsWithObjectTypeInclusion() { OutParameterClass c1 = new; int|error v1 = c1.get(int); - assert(1234, checkpanic v1); + assert(1234, checkpanic v1); assertSame(c1.c, c1.get(float)); - assert("hello world", checkpanic c1.get(string)); + assert("hello world", checkpanic c1.get(string)); - assert(321, checkpanic outParameterObject.get(int)); + assert(321, checkpanic outParameterObject.get(int)); decimal|error v2 = outParameterObject.get(decimal); - assert(23.45d, checkpanic v2); + assert(23.45d, checkpanic v2); } public class Bar { @@ -509,49 +516,49 @@ public function testSubtypingWithDependentlyTypedMethods() { Baz baz = new; Qux qux = new; - assert(1, checkpanic bar.get(int)); - assert(2, checkpanic baz.get(int)); - assert(3, checkpanic qux.get(int)); + assert(1, checkpanic bar.get(int)); + assert(2, checkpanic baz.get(int)); + assert(3, checkpanic qux.get(int)); decimal|error v2 = bar.get(decimal); - assert(23.45d, checkpanic v2); + assert(23.45d, checkpanic v2); anydata|error v3 = baz.get(decimal); - assert(23.45d, checkpanic v3); + assert(23.45d, checkpanic v3); v2 = qux.get(decimal); - assert(23.45d, checkpanic v2); + assert(23.45d, checkpanic v2); Baz baz1 = bar; Bar bar1 = qux; - assert(1, checkpanic baz1.get(int)); - assert(3, checkpanic bar1.get(int)); + assert(1, checkpanic baz1.get(int)); + assert(3, checkpanic bar1.get(int)); - assert(true, bar is Baz); - assert(true, qux is Bar); - assert(true, bar is Qux); - assert(false, baz is Bar); - assert(false, new Quux() is Qux); - assert(false, qux is Quux); + assert(true, bar is Baz); + assert(true, qux is Bar); + assert(true, bar is Qux); + assert(false, baz is Bar); + assert(false, new Quux() is Qux); + assert(false, qux is Quux); Corge corge = new Grault(); - assert(200, checkpanic corge.get(int, string)); - assert("Hello World!", checkpanic corge.get(string, int)); + assert(200, checkpanic corge.get(int, string)); + assert("Hello World!", checkpanic corge.get(string, int)); Grault grault = new Corge(); - assert(100, checkpanic grault.get(int, string)); - assert("Hello World!", checkpanic grault.get(string, float)); + assert(100, checkpanic grault.get(int, string)); + assert("Hello World!", checkpanic grault.get(string, float)); - assert(true, new Corge() is Grault); - assert(true, new Grault() is Corge); - assert(false, new Corge() is Garply); - assert(false, new Garply() is Corge); - assert(false, new Grault() is Garply); - assert(false, new Garply() is Grault); + assert(true, new Corge() is Grault); + assert(true, new Grault() is Corge); + assert(true, new Corge() is Garply); + assert(true, new Garply() is Corge); + assert(true, new Grault() is Garply); + assert(true, new Garply() is Grault); } function getWithDefaultableParams(int|string x, int|string y = 1, typedesc z = int) returns z = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "getWithDefaultableParams" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "getWithDefaultableParams" +} external; function testDependentlyTypedFunctionWithDefaultableParams() { int a = getWithDefaultableParams(1); @@ -584,12 +591,12 @@ type IntOrString int|string; public function testStartActionWithDependentlyTypedFunctions() { Client cl = new; - var assert1 = function (future f) { + var assert1 = function(future f) { int|string|error r = wait f; assert(true, r is error); - error e = r; + error e = r; assert("Error!", e.message()); - assert("Union typedesc", checkpanic e.detail()["message"]); + assert("Union typedesc", checkpanic e.detail()["message"]); }; future a = start getWithUnion("", IntOrString); assert1(a); @@ -598,7 +605,7 @@ public function testStartActionWithDependentlyTypedFunctions() { future c = start cl->remoteGet("", IntOrString); assert1(c); - var assert2 = function (future f, int expected) { + var assert2 = function(future f, int expected) { int|error r = wait f; assert(true, r is int); assert(expected, checkpanic r); @@ -612,7 +619,7 @@ public function testStartActionWithDependentlyTypedFunctions() { future g = start cl->remoteGet("hi", int); assert2(g, 2); - var assert3 = function (future f, string expected) { + var assert3 = function(future f, string expected) { string|error r = wait f; assert(true, r is string); assert(expected, checkpanic r); @@ -627,9 +634,9 @@ public function testStartActionWithDependentlyTypedFunctions() { function getWithUnion(int|string x, typedesc y) returns y|error = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "getWithUnion" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "getWithUnion" +} external; client class Client { function get(int|string x, typedesc y = int) returns y|error = @java:Method { @@ -644,12 +651,12 @@ client class Client { } function getWithRestParam(int i, typedesc j, int... k) returns j|boolean = - @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType" - } external; + @java:Method { + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType" +} external; function getWithMultipleTypedescs(int i, typedesc j, typedesc k, typedesc... l) - returns j|k|boolean = @java:Method { 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType" } external; + returns j|k|boolean = @java:Method {'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType"} external; function testArgsForDependentlyTypedFunctionViaTupleRestArg() { [typedesc] a = [string]; @@ -728,7 +735,7 @@ type IJK record {| |}; function testArgsForDependentlyTypedFunctionViaRecordRestArg() { - record {| typedesc y; |} a = {y: string}; + record {|typedesc y;|} a = {y: string}; string|error b = getWithUnion(123, ...a); assert("123", checkpanic b); @@ -740,7 +747,7 @@ function testArgsForDependentlyTypedFunctionViaRecordRestArg() { string|boolean f = getWithRestParam(...e); assert(true, f); - record {| typedesc j = string; |} g = {}; + record {|typedesc j = string;|} g = {}; string|boolean h = getWithRestParam(1, ...g); assert(false, h); @@ -748,11 +755,11 @@ function testArgsForDependentlyTypedFunctionViaRecordRestArg() { int|string|boolean n = getWithMultipleTypedescs(...m); assert(true, n); - record {| typedesc j = string; typedesc k; |} o = {k: int}; + record {|typedesc j = string; typedesc k;|} o = {k: int}; int|string|boolean p = getWithMultipleTypedescs(1, ...o); assert(true, p); - record {| int i; typedesc j = byte; typedesc k; |} q = {i: 1, k: byte}; + record {|int i; typedesc j = byte; typedesc k;|} q = {i: 1, k: byte}; byte|boolean r = getWithMultipleTypedescs(...q); assert(true, r); } @@ -767,29 +774,29 @@ public type TargetType typedesc; public client class ClientWithMethodWithIncludedRecordParamAndDefaultableParams { remote function post(TargetType targetType = int, *ClientActionOptions options) returns @tainted targetType = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "clientPost" - } external; - + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "clientPost" + } external; + function calculate(int i, TargetType targetType = int, *ClientActionOptions options) returns @tainted targetType = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "calculate" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "calculate" + } external; } public client class ClientWithMethodWithIncludedRecordParamAndRequiredParams { remote function post(TargetType targetType, *ClientActionOptions options) returns @tainted targetType = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "clientPost" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "clientPost" + } external; function calculate(int i, TargetType targetType, *ClientActionOptions options) returns @tainted targetType|error = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "calculate" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "calculate" + } external; } function testDependentlyTypedFunctionWithIncludedRecordParam() { @@ -885,57 +892,58 @@ function testDependentlyTypedFunctionWithIncludedRecordParam() { client class ClientObjImpl { *ClientObject; - remote isolated function query(stream strm, typedesc rowType = <>) returns stream = + + remote isolated function query(stream strm, typedesc rowType = <>) returns stream = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "getStreamOfRecords", - paramTypes: ["io.ballerina.runtime.api.values.BStream", "io.ballerina.runtime.api.values.BTypedesc"] - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "getStreamOfRecords", + paramTypes: ["io.ballerina.runtime.api.values.BStream", "io.ballerina.runtime.api.values.BTypedesc"] + } external; } public type ClientObject client object { - remote isolated function query(stream strm, typedesc rowType = <>) returns stream ; + remote isolated function query(stream strm, typedesc rowType = <>) returns stream; }; function testDependentlyTypedMethodCallOnObjectType() { ClientObject cl = new ClientObjImpl(); - Person p1 = getRecord(); - Person p2 = getRecord(); - Person[] personList = [p1, p2]; - stream personStream = personList.toStream(); - stream y = cl->query(personStream, Person); + PersonDTFT p1 = getRecord(); + PersonDTFT p2 = getRecord(); + PersonDTFT[] personList = [p1, p2]; + stream personStream = personList.toStream(); + stream y = cl->query(personStream, PersonDTFT); var rec = y.next(); - if (rec is record {| Person value; |}) { - Person person = rec.value; + if (rec is record {|PersonDTFT value;|}) { + PersonDTFT person = rec.value; assert(20, person.age); assert("John Doe", person.name); } rec = y.next(); - assert(true, rec is record {| Person value; |}); + assert(true, rec is record {|PersonDTFT value;|}); } function testDependentlyTypedMethodCallOnObjectTypeWithInferredArgument() { ClientObject cl = new ClientObjImpl(); - Person p1 = getRecord(); - Person p2 = getRecord(); - Person[] personList = [p1, p2]; - stream personStream = personList.toStream(); - stream y = cl->query(personStream); + PersonDTFT p1 = getRecord(); + PersonDTFT p2 = getRecord(); + PersonDTFT[] personList = [p1, p2]; + stream personStream = personList.toStream(); + stream y = cl->query(personStream); var rec = y.next(); - if (rec is record {| Person value; |}) { - Person person = rec.value; + if (rec is record {|PersonDTFT value;|}) { + PersonDTFT person = rec.value; assert(20, person.age); assert("John Doe", person.name); } rec = y.next(); - assert(true, rec is record {| Person value; |}); + assert(true, rec is record {|PersonDTFT value;|}); } function functionWithInferredArgForParamOfTypeReferenceType(TargetType t = <>) returns t = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "functionWithInferredArgForParamOfTypeReferenceType" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "functionWithInferredArgForParamOfTypeReferenceType" +} external; function testDependentlyTypedFunctionWithInferredArgForParamOfTypeReferenceType() { int a = functionWithInferredArgForParamOfTypeReferenceType(); @@ -963,18 +971,18 @@ function testDependentlyTypedResourceMethods() { ClientWithExternalResourceBody cl = new ClientWithExternalResourceBody(); string|error a = cl->/games/carrom(targetType = string); assert("[\"games\",\"carrom\"]", checkpanic a); - + var cl2 = client object { resource function get [string... path](TargetType targetType = <>) returns targetType|error = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", - name: "getResource" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", + name: "getResource" + } external; }; string|error b = cl2->/games/football(targetType = string); assert("[\"games\",\"football\"]", checkpanic b); - + int|error c = cl2->/games/football(); assert(0, checkpanic c); } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/inferred_dependently_typed_func_signature.bal b/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/inferred_dependently_typed_func_signature.bal index d1e9afa45b5e..06beac3920e1 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/inferred_dependently_typed_func_signature.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/javainterop/inferred_dependently_typed_func_signature.bal @@ -16,18 +16,17 @@ import ballerina/jballerina.java; -type Person record { +type PersonIDTF record { readonly string name; int age; }; -type Employee record { - *Person; +type EmployeeIDTF record { + *PersonIDTF; string designation; }; -Person expPerson = {name: "John Doe", age: 20}; - +PersonIDTF expPerson = {name: "John Doe", age: 20}; // Test functions @@ -49,11 +48,11 @@ function testSimpleTypes() { } function testRecordVarRef() { - Person p = getRecord(); + PersonIDTF p = getRecord(); assert(expPerson, p); - Employee e = getRecord(td = Employee); - assert({name: "Jane Doe", age: 25, designation: "Software Engineer"}, e); + EmployeeIDTF e = getRecord(td = EmployeeIDTF); + assert({name: "Jane Doe", age: 25, designation: "Software Engineer"}, e); } function testVarRefInMapConstraint() { @@ -69,15 +68,15 @@ function testRuntimeCastError() { error err = m1; assert("{ballerina}TypeCastError", err.message()); - assert("incompatible types: 'map' cannot be cast to 'map'", checkpanic err.detail()["message"]); + assert("incompatible types: 'map' cannot be cast to 'map'", checkpanic err.detail()["message"]); } function testTupleTypes() { - [int, Person, float] tup1 = getTuple(int, Person); - assert(<[int, Person, float]>[150, expPerson, 12.34], tup1); + [int, PersonIDTF, float] tup1 = getTuple(int, PersonIDTF); + assert(<[int, PersonIDTF, float]>[150, expPerson, 12.34], tup1); - [int, Person, boolean...] tup2 = getTupleWithRestDesc(int, Person); - assert(<[int, Person, boolean...]>[150, expPerson, true, true], tup2); + [int, PersonIDTF, boolean...] tup2 = getTupleWithRestDesc(int, PersonIDTF); + assert(<[int, PersonIDTF, boolean...]>[150, expPerson, true, true], tup2); tup2[4] = false; assert(5, tup2.length()); } @@ -88,11 +87,12 @@ function testArrayTypes() { } type XmlComment xml:Comment; + type XmlElement xml:Element; function testXml() { xml:Comment x1 = xml ``; - xml x2 = > x1.concat(xml ``); + xml x2 = >x1.concat(xml ``); xml a = getXml(val = x2); assert(x2, a); assert(2, a.length()); @@ -128,14 +128,14 @@ function testXml() { function testCastingForInvalidValues() { var fn = function() { - int x = getInvalidValue(td2 = Person); + int x = getInvalidValue(td2 = PersonIDTF); }; error? y = trap fn(); assert(true, y is error); - error err = y; + error err = y; assert("{ballerina}TypeCastError", err.message()); - assert("incompatible types: 'Person' cannot be cast to 'int'", checkpanic err.detail()["message"]); + assert("incompatible types: 'PersonIDTF' cannot be cast to 'int'", checkpanic err.detail()["message"]); } function testStream() { @@ -144,19 +144,21 @@ function testStream() { stream newSt = getStream(st); string s = ""; - error? err = newSt.forEach(function (string x) { s += x; }); + error? err = newSt.forEach(function(string x) { + s += x; + }); assert("helloworldfromballerina", s); } function testTable() { - table key(name) tab = table [ - { name: "Chiran", age: 33, designation: "SE" }, - { name: "Mohan", age: 37, designation: "SE" }, - { name: "Gima", age: 38, designation: "SE" }, - { name: "Granier", age: 34, designation: "SE" } + table key(name) tab = table [ + {name: "Chiran", age: 33, designation: "SE"}, + {name: "Mohan", age: 37, designation: "SE"}, + {name: "Gima", age: 38, designation: "SE"}, + {name: "Granier", age: 34, designation: "SE"} ]; - table newTab = getTable(tab); + table newTab = getTable(tab); assert(tab, newTab); } @@ -168,12 +170,12 @@ function testFunctionPointers() { } function testTypedesc() { - typedesc tP = getTypedesc(); - assert(Person.toString(), tP.toString()); + typedesc tP = getTypedesc(); + assert(PersonIDTF.toString(), tP.toString()); } function testFuture() { - var fn = function (string name) returns string => "Hello " + name; + var fn = function(string name) returns string => "Hello " + name; future f = start fn("Pubudu"); future fNew = getFuture(f, string); string res = checkpanic wait fNew; @@ -195,7 +197,7 @@ class PersonObj { type IntStream stream; -type PersonTable table; +type PersonTable table; type IntArray int[]; @@ -209,7 +211,7 @@ function testComplexTypes() { int[] ar = echo([20, 30, 40]); assert([20, 30, 40], ar); - PersonObj pObj = new("John", "Doe"); + PersonObj pObj = new ("John", "Doe"); PersonObj nObj = echo(pObj); assertSame(pObj, nObj); @@ -218,17 +220,19 @@ function testComplexTypes() { stream newSt = echo(st); int tot = 0; - error? err = newSt.forEach(function (int x1) { tot+= x1; }); + error? err = newSt.forEach(function(int x1) { + tot += x1; + }); assert(150, tot); - table key(name) tab = table [ - { name: "Chiran", age: 33}, - { name: "Mohan", age: 37}, - { name: "Gima", age: 38}, - { name: "Granier", age: 34} + table key(name) tab = table [ + {name: "Chiran", age: 33}, + {name: "Mohan", age: 37}, + {name: "Gima", age: 38}, + {name: "Granier", age: 34} ]; - table newTab = echo(tab); + table newTab = echo(tab); assert(tab, newTab); } @@ -244,7 +248,7 @@ function testFunctionAssignment() { error err = v; assert("{ballerina}TypeCastError", err.message()); - assert("incompatible types: 'string' cannot be cast to 'int'", checkpanic err.detail()["message"]); + assert("incompatible types: 'string' cannot be cast to 'int'", checkpanic err.detail()["message"]); } function testUnionTypes() { @@ -261,10 +265,10 @@ function testUnionTypes() { assert("hello", d); int[]|map? e = getComplexUnion(); - assert( [1, 2], e); + assert([1, 2], e); map<[int, string][]>|()|[int, string][] f = getComplexUnion(); - assert(> {entry: [[100, "Hello World"]]}, f); + assert(>{entry: [[100, "Hello World"]]}, f); int|boolean? g = getSimpleUnion(1, int); assert(1, g); @@ -279,47 +283,47 @@ function testUnionTypes() { assert("hello", j); int[]|map? k = getComplexUnion(int); - assert( [1, 2], k); + assert([1, 2], k); map<[int, string][]>|()|[int, string][] l = getComplexUnion(td = [int, string]); - assert(> {entry: [[100, "Hello World"]]}, l); + assert(>{entry: [[100, "Hello World"]]}, l); } function testArgCombinations() { int[] a = funcWithMultipleArgs(1, int, ["hello", "world"]); - assert( [2, 1], a); + assert([2, 1], a); string[] b = funcWithMultipleArgs(td = string, arr = ["hello", "world"], i = 3); - assert( ["hello", "world", "3"], b); + assert(["hello", "world", "3"], b); - record {| string[] arr = ["hello", "world", "Ballerina"]; int i = 123; typedesc td; |} rec1 = {td: int}; + record {|string[] arr = ["hello", "world", "Ballerina"]; int i = 123; typedesc td;|} rec1 = {td: int}; int[] c = funcWithMultipleArgs(...rec1); - assert( [3, 123], c); + assert([3, 123], c); - record {| string[] arr = ["hello", "world"]; |} rec2 = {}; + record {|string[] arr = ["hello", "world"];|} rec2 = {}; int[] d = funcWithMultipleArgs(1234, int, ...rec2); - assert( [2, 1234], d); + assert([2, 1234], d); [int, typedesc, string[]] tup1 = [21, string, ["hello"]]; string[] e = funcWithMultipleArgs(...tup1); - assert( ["hello", "21"], e); + assert(["hello", "21"], e); [string[]] tup2 = [["hello"]]; string[] f = funcWithMultipleArgs(34, string, ...tup2); - assert( ["hello", "34"], f); + assert(["hello", "34"], f); int[] g = funcWithMultipleArgs(1); - assert( [0, 1], g); + assert([0, 1], g); string[] h = funcWithMultipleArgs(101, arr = ["hello", "world"]); - assert( ["hello", "world", "101"], h); + assert(["hello", "world", "101"], h); int[] i = funcWithMultipleArgs(arr = ["hello", "world"], i = 202); - assert( [2, 202], i); + assert([2, 202], i); } function testBuiltInRefType() { - stream strm = ( [1, 2, 3]).toStream(); + stream strm = ([1, 2, 3]).toStream(); readonly|handle|stream a = funcReturningUnionWithBuiltInRefType(strm); assertSame(strm, a); @@ -331,65 +335,65 @@ function testBuiltInRefType() { assertSame(strm, d); stream|readonly e = funcReturningUnionWithBuiltInRefType(strm); assert(true, e is handle); - string? str = java:toString( checkpanic e); + string? str = java:toString(checkpanic e); assert("hello world", str); } function testParameterizedTypeInUnionWithNonParameterizedTypes() { - record {| stream x; |} rec = {x: ( [1, 2, 3]).toStream()}; - object {}|record {| stream x; |}|int|error a = getValueWithUnionReturnType(rec); - assert(101, checkpanic a); + record {|stream x;|} rec = {x: ([1, 2, 3]).toStream()}; + object {}|record {|stream x;|}|int|error a = getValueWithUnionReturnType(rec); + assert(101, checkpanic a); PersonObj pObj = new ("John", "Doe"); - object {}|record {| stream x; |}|string[]|error b = getValueWithUnionReturnType(pObj); + object {}|record {|stream x;|}|string[]|error b = getValueWithUnionReturnType(pObj); assertSame(pObj, b); - error|object {}|record {| stream x; |}|boolean c = getValueWithUnionReturnType(true, boolean); - assert(false, checkpanic c); + error|object {}|record {|stream x;|}|boolean c = getValueWithUnionReturnType(true, boolean); + assert(false, checkpanic c); - error|object {}|record {| stream x; |}|boolean d = getValueWithUnionReturnType(td = boolean, val = false); - assert(true, checkpanic d); + error|object {}|record {|stream x;|}|boolean d = getValueWithUnionReturnType(td = boolean, val = false); + assert(true, checkpanic d); } function testUsageWithVarWithUserSpecifiedArg() { - stream strm = ( [1, 2, 3]).toStream(); + stream strm = ([1, 2, 3]).toStream(); var x = funcReturningUnionWithBuiltInRefType(strm, IntStream); assertSame(strm, x); } - function testFunctionWithAnyFunctionParamType() { - var fn = function (function x, int y) { +function testFunctionWithAnyFunctionParamType() { + var fn = function(function x, int y) { }; function (function, int) z = getFunctionWithAnyFunctionParamType(fn); assertSame(fn, z); - } +} function testUsageWithCasts() { - int a = getValue(); + int a = getValue(); assert(150, a); - var b = getValue(); + var b = getValue(); assert(12.34, b); - any c = getValue(); - assert(23.45d, c); + any c = getValue(); + assert(23.45d, c); - string|xml|float d = getValue(); + string|xml|float d = getValue(); assert("Hello World!", d); - anydata e = getValue(); + anydata e = getValue(); assert(true, e); - anydata f = <[int, Person, boolean...]> getTupleWithRestDesc(int, Person); - assert(<[int, Person, boolean...]>[150, expPerson, true, true], f); + anydata f = <[int, PersonIDTF, boolean...]>getTupleWithRestDesc(int, PersonIDTF); + assert(<[int, PersonIDTF, boolean...]>[150, expPerson, true, true], f); - record {| stream x; |} g = {x: ( [1, 2, 3]).toStream()}; - var h = x; |}|int> checkpanic getValueWithUnionReturnType(g); - assert(101, h); + record {|stream x;|} g = {x: ([1, 2, 3]).toStream()}; + var h = x;|}|int>checkpanic getValueWithUnionReturnType(g); + assert(101, h); PersonObj i = new ("John", "Doe"); - any|error j = x; |}|string[]|error> getValueWithUnionReturnType(i); + any|error j = x;|}|string[]|error>getValueWithUnionReturnType(i); assertSame(i, j); } @@ -420,7 +424,7 @@ function getTuple(typedesc td1, typedesc td2, typedesc td1, typedesc td2, typedesc td3 = <>) returns [td1, td2, td3...] = - @java:Method { 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType" } external; + @java:Method {'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType"} external; function getArray(typedesc td = <>) returns td[] = @java:Method { 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", @@ -430,10 +434,10 @@ function getArray(typedesc td = <>) returns td[] = @java:Method { function getXml(typedesc td = <>, xml val = xml ``) returns xml = @java:Method { - 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType" - } external; + 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType" +} external; -function getInvalidValue(typedesc td1 = <>, typedesc td2 = Person) returns td1 = +function getInvalidValue(typedesc td1 = <>, typedesc td2 = PersonIDTF) returns td1 = @java:Method { 'class: "org.ballerinalang.nativeimpl.jvm.tests.VariableReturnType", name: "getInvalidValue", diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/query/multiple-order-by-clauses.bal b/tests/jballerina-unit-test/src/test/resources/test-src/query/multiple-order-by-clauses.bal index 9fd7422c55fc..c6ae94051aa2 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/query/multiple-order-by-clauses.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/query/multiple-order-by-clauses.bal @@ -14,59 +14,59 @@ // specific language governing permissions and limitations // under the License. -type Student record {| - readonly int id; - string? fname; - float fee; - decimal impact; - boolean isUndergrad; +type StudentY record {| + readonly int id; + string? fname; + float fee; + decimal impact; + boolean isUndergrad; |}; -type Person record {| +type PersonY record {| string firstName; string lastName; int age; string address; |}; -type Customer record {| +type CustomerY record {| readonly int id; readonly string name; int noOfItems; |}; -type CustomerProfile record {| +type CustomerProfileY record {| string name; int age; int noOfItems; string address; |}; -type StudentTable table key(id); +type StudentTableY table key(id); function testQueryExprWithMultipleOrderByClauses() returns boolean { boolean testPassed = true; - Student s1 = {id: 1, fname: "John", fee: 2000.56, impact: 0.4, isUndergrad: true}; - Student s2 = {id: 2, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; - Student s3 = {id: 2, fname: (), fee: 4000.56, impact: 0.4, isUndergrad: true}; - Student s4 = {id: 2, fname: "Kate", fee: 2000.56, impact: 0.4, isUndergrad: true}; - Student s5 = {id: 3, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; + StudentY s1 = {id: 1, fname: "John", fee: 2000.56, impact: 0.4, isUndergrad: true}; + StudentY s2 = {id: 2, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; + StudentY s3 = {id: 2, fname: (), fee: 4000.56, impact: 0.4, isUndergrad: true}; + StudentY s4 = {id: 2, fname: "Kate", fee: 2000.56, impact: 0.4, isUndergrad: true}; + StudentY s5 = {id: 3, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; - Student[] studentList = [s1, s2, s3, s4, s5]; + StudentY[] studentList = [s1, s2, s3, s4, s5]; - Student[] opStudentList = + StudentY[] opStudentList = from var student in studentList - order by student.fname descending, student.impact - order by student.id descending - select student; + order by student.fname descending, student.impact + order by student.id descending + select student; testPassed = testPassed && opStudentList.length() == 5; - testPassed = testPassed && opStudentList[0] == studentList[4]; - testPassed = testPassed && opStudentList[1] == studentList[3]; - testPassed = testPassed && opStudentList[2] == studentList[1]; - testPassed = testPassed && opStudentList[3] == studentList[2]; - testPassed = testPassed && opStudentList[4] == studentList[0]; + testPassed = testPassed && opStudentList[0] == studentList[4]; + testPassed = testPassed && opStudentList[1] == studentList[3]; + testPassed = testPassed && opStudentList[2] == studentList[1]; + testPassed = testPassed && opStudentList[3] == studentList[2]; + testPassed = testPassed && opStudentList[4] == studentList[0]; return testPassed; } @@ -74,32 +74,32 @@ function testQueryExprWithMultipleOrderByClauses() returns boolean { function testQueryExprWithMultipleFromAndMultipleOrderByClauses() returns boolean { boolean testPassed = true; - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 5, name: "James", noOfItems: 5}; - Customer c3 = {id: 7, name: "James", noOfItems: 25}; - Customer c4 = {id: 0, name: "James", noOfItems: 25}; + CustomerY c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerY c2 = {id: 5, name: "James", noOfItems: 5}; + CustomerY c3 = {id: 7, name: "James", noOfItems: 25}; + CustomerY c4 = {id: 0, name: "James", noOfItems: 25}; - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23, address: "New York"}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30, address: "California"}; + PersonY p1 = {firstName: "Amy", lastName: "Melina", age: 23, address: "New York"}; + PersonY p2 = {firstName: "Frank", lastName: "James", age: 30, address: "California"}; - Customer[] customerList = [c1, c2, c3, c4]; - Person[] personList = [p1, p2]; + CustomerY[] customerList = [c1, c2, c3, c4]; + PersonY[] personList = [p1, p2]; - Customer[] opCustomerList = + CustomerY[] opCustomerList = from var customer in customerList - from var person in personList - let string customerName = "Johns" - where person.lastName == customer.name - order by customer.id descending - order by person.address - select { - id: customer.id, - name: customerName, - noOfItems: customer.noOfItems - }; + from var person in personList + let string customerName = "Johns" + where person.lastName == customer.name + order by customer.id descending + order by person.address + select { + id: customer.id, + name: customerName, + noOfItems: customer.noOfItems + }; testPassed = testPassed && opCustomerList.length() == 4; - Customer cus; + CustomerY cus; cus = opCustomerList[0]; testPassed = testPassed && cus.id == 7 && cus.noOfItems == 25; cus = opCustomerList[1]; @@ -114,32 +114,32 @@ function testQueryExprWithMultipleFromAndMultipleOrderByClauses() returns boolea function testQueryExprWithJoinAndMultipleOrderByClauses() returns boolean { boolean testPassed = true; - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "James", noOfItems: 25}; - Customer c4 = {id: 4, name: "James", noOfItems: 25}; + CustomerY c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerY c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerY c3 = {id: 3, name: "James", noOfItems: 25}; + CustomerY c4 = {id: 4, name: "James", noOfItems: 25}; - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23, address: "New York"}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30, address: "California"}; + PersonY p1 = {firstName: "Amy", lastName: "Melina", age: 23, address: "New York"}; + PersonY p2 = {firstName: "Frank", lastName: "James", age: 30, address: "California"}; - Customer[] customerList = [c1, c2, c3, c4]; - Person[] personList = [p1, p2]; + CustomerY[] customerList = [c1, c2, c3, c4]; + PersonY[] personList = [p1, p2]; - CustomerProfile[] customerProfileList = + CustomerProfileY[] customerProfileList = from var customer in customerList - join var person in personList + join var person in personList on customer.name equals person.lastName - order by customer.noOfItems descending - order by person.address - select { - name: person.firstName, - age : person.age, - noOfItems: customer.noOfItems, - address: person.address - }; + order by customer.noOfItems descending + order by person.address + select { + name: person.firstName, + age: person.age, + noOfItems: customer.noOfItems, + address: person.address + }; testPassed = testPassed && customerProfileList.length() == 4; - CustomerProfile cp; + CustomerProfileY cp; cp = customerProfileList[0]; testPassed = testPassed && cp.name == "Frank" && cp.age == 30 && cp.noOfItems == 25 && cp.address == "California"; cp = customerProfileList[1]; @@ -154,36 +154,40 @@ function testQueryExprWithJoinAndMultipleOrderByClauses() returns boolean { function testQueryExprWithInnerQueriesAndMultipleOrderByClauses() returns boolean { boolean testPassed = true; - Customer c1 = {id: 1, name: "Melina", noOfItems: 62}; - Customer c2 = {id: 5, name: "James", noOfItems: 5}; - Customer c3 = {id: 9, name: "James", noOfItems: 25}; - Customer c4 = {id: 0, name: "James", noOfItems: 25}; - Customer c5 = {id: 2, name: "James", noOfItems: 30}; + CustomerY c1 = {id: 1, name: "Melina", noOfItems: 62}; + CustomerY c2 = {id: 5, name: "James", noOfItems: 5}; + CustomerY c3 = {id: 9, name: "James", noOfItems: 25}; + CustomerY c4 = {id: 0, name: "James", noOfItems: 25}; + CustomerY c5 = {id: 2, name: "James", noOfItems: 30}; - Person p1 = {firstName: "Jennifer", lastName: "Melina", age: 23, address: "California"}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30, address: "New York"}; - Person p3 = {firstName: "Zeth", lastName: "James", age: 50, address: "Texas"}; + PersonY p1 = {firstName: "Jennifer", lastName: "Melina", age: 23, address: "California"}; + PersonY p2 = {firstName: "Frank", lastName: "James", age: 30, address: "New York"}; + PersonY p3 = {firstName: "Zeth", lastName: "James", age: 50, address: "Texas"}; - Customer[] customerList = [c1, c2, c3, c4, c5]; - Person[] personList = [p1, p2, p3]; + CustomerY[] customerList = [c1, c2, c3, c4, c5]; + PersonY[] personList = [p1, p2, p3]; - CustomerProfile[] customerProfileList = + CustomerProfileY[] customerProfileList = from var customer in (stream from var c in customerList - order by c.id descending limit 4 select c) - join var person in (from var p in personList order by p.firstName descending select p) - on customer.name equals person.lastName - order by customer.noOfItems descending - order by person.address + order by c.id descending limit 4 - select { - name: person.firstName, - age : person.age, - noOfItems: customer.noOfItems, - address: person.address - }; + select c) + join var person in (from var p in personList + order by p.firstName descending + select p) + on customer.name equals person.lastName + order by customer.noOfItems descending + order by person.address + limit 4 + select { + name: person.firstName, + age: person.age, + noOfItems: customer.noOfItems, + address: person.address + }; testPassed = testPassed && customerProfileList.length() == 4; - CustomerProfile cp; + CustomerProfileY cp; cp = customerProfileList[0]; testPassed = testPassed && cp.name == "Jennifer" && cp.age == 23 && cp.noOfItems == 62 && cp.address == "California"; @@ -199,37 +203,40 @@ function testQueryExprWithInnerQueriesAndMultipleOrderByClauses() returns boolea function testQueryExprWithMultipleOrderByAndMultipleLimitClauses() returns boolean { boolean testPassed = true; - Customer c1 = {id: 1, name: "Melina", noOfItems: 62}; - Customer c2 = {id: 5, name: "James", noOfItems: 5}; - Customer c3 = {id: 9, name: "James", noOfItems: 25}; - Customer c4 = {id: 0, name: "James", noOfItems: 25}; - Customer c5 = {id: 2, name: "James", noOfItems: 30}; + CustomerY c1 = {id: 1, name: "Melina", noOfItems: 62}; + CustomerY c2 = {id: 5, name: "James", noOfItems: 5}; + CustomerY c3 = {id: 9, name: "James", noOfItems: 25}; + CustomerY c4 = {id: 0, name: "James", noOfItems: 25}; + CustomerY c5 = {id: 2, name: "James", noOfItems: 30}; - Person p1 = {firstName: "Jennifer", lastName: "Melina", age: 23, address: "California"}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30, address: "New York"}; - Person p3 = {firstName: "Zeth", lastName: "James", age: 50, address: "Texas"}; + PersonY p1 = {firstName: "Jennifer", lastName: "Melina", age: 23, address: "California"}; + PersonY p2 = {firstName: "Frank", lastName: "James", age: 30, address: "New York"}; + PersonY p3 = {firstName: "Zeth", lastName: "James", age: 50, address: "Texas"}; - Customer[] customerList = [c1, c2, c3, c4, c5]; - Person[] personList = [p1, p2, p3]; + CustomerY[] customerList = [c1, c2, c3, c4, c5]; + PersonY[] personList = [p1, p2, p3]; - CustomerProfile[] customerProfileList = + CustomerProfileY[] customerProfileList = from var customer in (stream from var c in customerList - order by c.id descending select c) - join var person in (from var p in personList order by p.firstName descending select p) + order by c.id descending + select c) + join var person in (from var p in personList + order by p.firstName descending + select p) on customer.name equals person.lastName - order by customer.noOfItems descending - limit 5 - order by person.address - limit 2 - select { - name: person.firstName, - age : person.age, - noOfItems: customer.noOfItems, - address: person.address - }; + order by customer.noOfItems descending + limit 5 + order by person.address + limit 2 + select { + name: person.firstName, + age: person.age, + noOfItems: customer.noOfItems, + address: person.address + }; testPassed = testPassed && customerProfileList.length() == 2; - CustomerProfile cp; + CustomerProfileY cp; cp = customerProfileList[0]; testPassed = testPassed && cp.name == "Jennifer" && cp.age == 23 && cp.noOfItems == 62 && cp.address == "California"; @@ -240,37 +247,41 @@ function testQueryExprWithMultipleOrderByAndMultipleLimitClauses() returns boole function testQueryActionWithMultipleOrderByClauses() returns boolean { boolean testPassed = true; - CustomerProfile[] customerProfileList = []; + CustomerProfileY[] customerProfileList = []; - Customer c1 = {id: 1, name: "Melina", noOfItems: 62}; - Customer c2 = {id: 5, name: "James", noOfItems: 5}; - Customer c3 = {id: 9, name: "James", noOfItems: 25}; - Customer c4 = {id: 0, name: "James", noOfItems: 25}; - Customer c5 = {id: 2, name: "James", noOfItems: 30}; + CustomerY c1 = {id: 1, name: "Melina", noOfItems: 62}; + CustomerY c2 = {id: 5, name: "James", noOfItems: 5}; + CustomerY c3 = {id: 9, name: "James", noOfItems: 25}; + CustomerY c4 = {id: 0, name: "James", noOfItems: 25}; + CustomerY c5 = {id: 2, name: "James", noOfItems: 30}; - Person p1 = {firstName: "Jennifer", lastName: "Melina", age: 23, address: "California"}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30, address: "New York"}; - Person p3 = {firstName: "Zeth", lastName: "James", age: 50, address: "Texas"}; + PersonY p1 = {firstName: "Jennifer", lastName: "Melina", age: 23, address: "California"}; + PersonY p2 = {firstName: "Frank", lastName: "James", age: 30, address: "New York"}; + PersonY p3 = {firstName: "Zeth", lastName: "James", age: 50, address: "Texas"}; - Customer[] customerList = [c1, c2, c3, c4, c5]; - Person[] personList = [p1, p2, p3]; + CustomerY[] customerList = [c1, c2, c3, c4, c5]; + PersonY[] personList = [p1, p2, p3]; error? op = from var customer in customerList - join var person in personList + join var person in personList on customer.name equals person.lastName - order by customer.noOfItems descending - limit 5 - order by person.address - limit 2 - do { - CustomerProfile cp = {name: person.firstName, age : person.age, noOfItems: customer.noOfItems, - address: person.address}; - customerProfileList[customerProfileList.length()] = cp; + order by customer.noOfItems descending + limit 5 + order by person.address + limit 2 + do { + CustomerProfileY cp = { + name: person.firstName, + age: person.age, + noOfItems: customer.noOfItems, + address: person.address }; + customerProfileList[customerProfileList.length()] = cp; + }; testPassed = testPassed && customerProfileList.length() == 2; - CustomerProfile cp; + CustomerProfileY cp; cp = customerProfileList[0]; testPassed = testPassed && cp.name == "Jennifer" && cp.age == 23 && cp.noOfItems == 62 && cp.address == "California"; @@ -282,27 +293,27 @@ function testQueryActionWithMultipleOrderByClauses() returns boolean { function testQueryExprWithMultipleOrderByClausesReturnTable() returns boolean { boolean testPassed = true; - Student s1 = {id: 1, fname: "John", fee: 2000.56, impact: 0.4, isUndergrad: true}; - Student s2 = {id: 2, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; - Student s3 = {id: 9, fname: (), fee: 4000.56, impact: 0.4, isUndergrad: true}; - Student s4 = {id: 4, fname: "Kate", fee: 2000.56, impact: 0.4, isUndergrad: true}; - Student s5 = {id: 10, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; + StudentY s1 = {id: 1, fname: "John", fee: 2000.56, impact: 0.4, isUndergrad: true}; + StudentY s2 = {id: 2, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; + StudentY s3 = {id: 9, fname: (), fee: 4000.56, impact: 0.4, isUndergrad: true}; + StudentY s4 = {id: 4, fname: "Kate", fee: 2000.56, impact: 0.4, isUndergrad: true}; + StudentY s5 = {id: 10, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; - Student[] studentList = [s1, s2, s3, s4, s5]; + StudentY[] studentList = [s1, s2, s3, s4, s5]; - StudentTable|error opStudentTable = + StudentTableY|error opStudentTable = table key(id) from var student in studentList - order by student.fname descending, student.impact - order by student.id descending - select student; - - if (opStudentTable is StudentTable) { - Student[] opStudentList = opStudentTable.toArray(); - testPassed = testPassed && opStudentList[0] == studentList[4]; - testPassed = testPassed && opStudentList[1] == studentList[2]; - testPassed = testPassed && opStudentList[2] == studentList[3]; - testPassed = testPassed && opStudentList[3] == studentList[1]; - testPassed = testPassed && opStudentList[4] == studentList[0]; + order by student.fname descending, student.impact + order by student.id descending + select student; + + if (opStudentTable is StudentTableY) { + StudentY[] opStudentList = opStudentTable.toArray(); + testPassed = testPassed && opStudentList[0] == studentList[4]; + testPassed = testPassed && opStudentList[1] == studentList[2]; + testPassed = testPassed && opStudentList[2] == studentList[3]; + testPassed = testPassed && opStudentList[3] == studentList[1]; + testPassed = testPassed && opStudentList[4] == studentList[0]; } return testPassed; } @@ -310,23 +321,23 @@ function testQueryExprWithMultipleOrderByClausesReturnTable() returns boolean { function testQueryExprWithMultipleOrderByClausesReturnStream() returns boolean { boolean testPassed = true; - Student s1 = {id: 1, fname: "John", fee: 2000.56, impact: 0.4, isUndergrad: true}; - Student s2 = {id: 2, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; - Student s3 = {id: 9, fname: (), fee: 4000.56, impact: 0.4, isUndergrad: true}; - Student s4 = {id: 4, fname: "Kate", fee: 2000.56, impact: 0.4, isUndergrad: true}; - Student s5 = {id: 10, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; + StudentY s1 = {id: 1, fname: "John", fee: 2000.56, impact: 0.4, isUndergrad: true}; + StudentY s2 = {id: 2, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; + StudentY s3 = {id: 9, fname: (), fee: 4000.56, impact: 0.4, isUndergrad: true}; + StudentY s4 = {id: 4, fname: "Kate", fee: 2000.56, impact: 0.4, isUndergrad: true}; + StudentY s5 = {id: 10, fname: "John", fee: 2000.56, impact: 0.45, isUndergrad: true}; - Student[] studentList = [s1, s2, s3, s4, s5]; + StudentY[] studentList = [s1, s2, s3, s4, s5]; - stream opStudentStream = + stream opStudentStream = stream from var student in studentList - order by student.fname descending, student.impact - order by student.id descending - select student; + order by student.fname descending, student.impact + order by student.id descending + select student; - Student[] opStudentList = []; - record {| Student value; |}|error? v = opStudentStream.next(); - while (v is record {| Student value; |}) { + StudentY[] opStudentList = []; + record {|StudentY value;|}|error? v = opStudentStream.next(); + while (v is record {|StudentY value;|}) { opStudentList.push(v.value); v = opStudentStream.next(); } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/query/order-by-clause.bal b/tests/jballerina-unit-test/src/test/resources/test-src/query/order-by-clause.bal index f1edb6added6..8bda7094c615 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/query/order-by-clause.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/query/order-by-clause.bal @@ -22,13 +22,13 @@ type Student record {| boolean isUndergrad; |}; -type Person record {| +type PersonPos record {| string firstName; string lastName; int age; |}; -type Customer record {| +type CustomerPos record {| readonly int id; readonly string name; int noOfItems; @@ -57,25 +57,25 @@ type PaymentInfo record {| string modeOfPayment; |}; -type CustomerTable table key(id, name); +type CustomerTable table key(id, name); type CustomerValue record {| - Customer value; + CustomerPos value; |}; type PersonValue record {| - Person value; + PersonPos value; |}; -function getCustomer(record {| Customer value; |}? returnedVal) returns Customer? { +function getCustomer(record {|CustomerPos value;|}? returnedVal) returns CustomerPos? { if (returnedVal is CustomerValue) { - return returnedVal.value; + return returnedVal.value; } else { - return (); + return (); } } -function getPersonValue((record {| Person value; |}|error?)|(record {| Person value; |}?) returnedVal) +function getPersonValue((record {|PersonPos value;|}|error?)|(record {|PersonPos value;|}?) returnedVal) returns PersonValue? { var result = returnedVal; if (result is PersonValue) { @@ -96,13 +96,13 @@ function testQueryExprWithOrderByClause() returns boolean { Student[] studentList = [s1, s2, s3, s4]; Student[] opStudentList = from var student in studentList - order by student.fname descending, student.impact - select student; + order by student.fname descending, student.impact + select student; - testPassed = testPassed && opStudentList[0] == studentList[3]; - testPassed = testPassed && opStudentList[1] == studentList[0]; - testPassed = testPassed && opStudentList[2] == studentList[1]; - testPassed = testPassed && opStudentList[3] == studentList[2]; + testPassed = testPassed && opStudentList[0] == studentList[3]; + testPassed = testPassed && opStudentList[1] == studentList[0]; + testPassed = testPassed && opStudentList[2] == studentList[1]; + testPassed = testPassed && opStudentList[3] == studentList[2]; return testPassed; } @@ -118,71 +118,71 @@ function testQueryExprWithOrderByClause2() returns boolean { Student[] studentList = [s1, s2, s3, s4]; Student[] opStudentList = from var student in studentList - order by student.isUndergrad ascending, student.fee - select student; + order by student.isUndergrad ascending, student.fee + select student; - testPassed = testPassed && opStudentList[0] == studentList[1]; - testPassed = testPassed && opStudentList[1] == studentList[2]; - testPassed = testPassed && opStudentList[2] == studentList[0]; - testPassed = testPassed && opStudentList[3] == studentList[3]; + testPassed = testPassed && opStudentList[0] == studentList[1]; + testPassed = testPassed && opStudentList[1] == studentList[2]; + testPassed = testPassed && opStudentList[2] == studentList[0]; + testPassed = testPassed && opStudentList[3] == studentList[3]; return testPassed; } -function testQueryExprWithOrderByClause3() returns Customer[] { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 5, name: "James", noOfItems: 5}; - Customer c3 = {id: 7, name: "James", noOfItems: 25}; - Customer c4 = {id: 0, name: "James", noOfItems: 25}; - - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; - - Customer[] customerList = [c1, c2, c3, c4]; - Person[] personList = [p1, p2]; - - Customer[] opCustomerList = from var customer in customerList - from var person in personList - let string customerName = "Johns" - where person.lastName == "James" - order by customer.id descending - select { - id: customer.id, - name: customerName, - noOfItems: customer.noOfItems - }; - - return opCustomerList; +function testQueryExprWithOrderByClause3() returns CustomerPos[] { + CustomerPos c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerPos c2 = {id: 5, name: "James", noOfItems: 5}; + CustomerPos c3 = {id: 7, name: "James", noOfItems: 25}; + CustomerPos c4 = {id: 0, name: "James", noOfItems: 25}; + + PersonPos p1 = {firstName: "Amy", lastName: "Melina", age: 23}; + PersonPos p2 = {firstName: "Frank", lastName: "James", age: 30}; + + CustomerPos[] customerList = [c1, c2, c3, c4]; + PersonPos[] personList = [p1, p2]; + + CustomerPos[] opCustomerList = from var customer in customerList + from var person in personList + let string customerName = "Johns" + where person.lastName == "James" + order by customer.id descending + select { + id: customer.id, + name: customerName, + noOfItems: customer.noOfItems + }; + + return opCustomerList; } function testQueryExprWithOrderByClauseReturnTable() returns boolean { boolean testPassed = true; - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "James", noOfItems: 25}; - Customer c4 = {id: 4, name: "James", noOfItems: 25}; + CustomerPos c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerPos c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerPos c3 = {id: 3, name: "James", noOfItems: 25}; + CustomerPos c4 = {id: 4, name: "James", noOfItems: 25}; - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonPos p1 = {firstName: "Amy", lastName: "Melina", age: 23}; + PersonPos p2 = {firstName: "Frank", lastName: "James", age: 30}; - Customer[] customerList = [c1, c2, c3, c4]; - Person[] personList = [p1, p2]; + CustomerPos[] customerList = [c1, c2, c3, c4]; + PersonPos[] personList = [p1, p2]; CustomerTable|error customerTable = table key(id, name) from var customer in customerList - from var person in personList - where person.firstName == "Frank" - order by customer.noOfItems descending, customer.id - limit 3 - select { - id: customer.id, - name: customer.name, - noOfItems: customer.noOfItems - }; + from var person in personList + where person.firstName == "Frank" + order by customer.noOfItems descending, customer.id + limit 3 + select { + id: customer.id, + name: customer.name, + noOfItems: customer.noOfItems + }; if (customerTable is CustomerTable) { var itr = customerTable.iterator(); - Customer? customer = getCustomer(itr.next()); + CustomerPos? customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[2]; customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[3]; @@ -198,19 +198,19 @@ function testQueryExprWithOrderByClauseReturnTable() returns boolean { function testQueryExprWithOrderByClauseReturnStream() returns boolean { boolean testPassed = true; - Person p1 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p2 = {firstName: "John", lastName: "David", age: 33}; - Person p3 = {firstName: "John", lastName: "Fonseka", age: 28}; - Person p4 = {firstName: "John", lastName: "Fonseka", age: 30}; - Person p5 = {firstName: "John", lastName: "Fonseka", age: 20}; + PersonPos p1 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonPos p2 = {firstName: "John", lastName: "David", age: 33}; + PersonPos p3 = {firstName: "John", lastName: "Fonseka", age: 28}; + PersonPos p4 = {firstName: "John", lastName: "Fonseka", age: 30}; + PersonPos p5 = {firstName: "John", lastName: "Fonseka", age: 20}; - Customer c1 = {id: 1, name: "John", noOfItems: 25}; - Customer c2 = {id: 2, name: "Frank", noOfItems: 20}; + CustomerPos c1 = {id: 1, name: "John", noOfItems: 25}; + CustomerPos c2 = {id: 2, name: "Frank", noOfItems: 20}; - Person[] personList = [p1, p2, p3, p4, p5]; - Customer[] customerList = [c1, c2]; + PersonPos[] personList = [p1, p2, p3, p4, p5]; + CustomerPos[] customerList = [c1, c2]; - stream outputPersonStream = stream from var person in personList.toStream() + stream outputPersonStream = stream from var person in personList.toStream() from var customer in customerList let string newLastName = "Turin" let string newFirstName = "Johnas" @@ -223,7 +223,7 @@ function testQueryExprWithOrderByClauseReturnStream() returns boolean { age: person.age }; - record {| Person value; |}? person = getPersonValue(outputPersonStream.next()); + record {|PersonPos value;|}? person = getPersonValue(outputPersonStream.next()); testPassed = testPassed && person?.value?.firstName == "Johnas" && person?.value?.lastName == "Turin" && person?.value?.age == 30; @@ -246,26 +246,26 @@ function testQueryExprWithOrderByClauseReturnStream() returns boolean { } function testQueryExprWithOrderByClauseAndJoin() returns CustomerProfile[] { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "James", noOfItems: 25}; - Customer c4 = {id: 4, name: "James", noOfItems: 25}; + CustomerPos c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerPos c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerPos c3 = {id: 3, name: "James", noOfItems: 25}; + CustomerPos c4 = {id: 4, name: "James", noOfItems: 25}; - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonPos p1 = {firstName: "Amy", lastName: "Melina", age: 23}; + PersonPos p2 = {firstName: "Frank", lastName: "James", age: 30}; - Customer[] customerList = [c1, c2, c3, c4]; - Person[] personList = [p1, p2]; + CustomerPos[] customerList = [c1, c2, c3, c4]; + PersonPos[] personList = [p1, p2]; CustomerProfile[] customerProfileList = from var customer in customerList - join var person in personList - on customer.name equals person.lastName - order by customer.noOfItems - select { - name: person.firstName, - age : person.age, - noOfItems: customer.noOfItems - }; + join var person in personList + on customer.name equals person.lastName + order by customer.noOfItems + select { + name: person.firstName, + age: person.age, + noOfItems: customer.noOfItems + }; return customerProfileList; } @@ -273,14 +273,30 @@ function testQueryExprWithOrderByClauseAndJoin() returns CustomerProfile[] { function testQueryExprWithOrderByClauseHavingUserDefinedOrderKeyFunction() returns boolean { boolean testPassed = true; - Employee e1 = {name: "Frank", address: {unitNo: 111, street: "Main Street"}, tokens: {one:1, two:2, three:3}, - noOfShifts: [1, 2, 3]}; - Employee e2 = {name: "James", address: {unitNo: 222, street: "Main Street"}, tokens: {one:1, two:2, three:3}, - noOfShifts: [1, 2, 3]}; - Employee e3 = {name: "James", address: {unitNo: 222, street: "Cross Street"}, tokens: {one:1, two:2, three:3}, - noOfShifts: [1, 2, 3]}; - Employee e4 = {name: "Frank", address: {unitNo: 111, street: "Cross Street"}, tokens: {one:1, two:2, three:3}, - noOfShifts: [1, 2, 3]}; + Employee e1 = { + name: "Frank", + address: {unitNo: 111, street: "Main Street"}, + tokens: {one: 1, two: 2, three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e2 = { + name: "James", + address: {unitNo: 222, street: "Main Street"}, + tokens: {one: 1, two: 2, three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e3 = { + name: "James", + address: {unitNo: 222, street: "Cross Street"}, + tokens: {one: 1, two: 2, three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e4 = { + name: "Frank", + address: {unitNo: 111, street: "Cross Street"}, + tokens: {one: 1, two: 2, three: 3}, + noOfShifts: [1, 2, 3] + }; Employee[] empList = [e1, e2, e3, e4]; @@ -288,10 +304,10 @@ function testQueryExprWithOrderByClauseHavingUserDefinedOrderKeyFunction() retur order by emp.address.unitNo descending, emp.address.street.toLowerAscii() select emp; - testPassed = testPassed && opEmpList[0] == empList[2]; - testPassed = testPassed && opEmpList[1] == empList[1]; - testPassed = testPassed && opEmpList[2] == empList[3]; - testPassed = testPassed && opEmpList[3] == empList[0]; + testPassed = testPassed && opEmpList[0] == empList[2]; + testPassed = testPassed && opEmpList[1] == empList[1]; + testPassed = testPassed && opEmpList[2] == empList[3]; + testPassed = testPassed && opEmpList[3] == empList[0]; return testPassed; } @@ -299,16 +315,36 @@ function testQueryExprWithOrderByClauseHavingUserDefinedOrderKeyFunction() retur function testQueryExprWithOrderByClauseHavingUserDefinedOrderKeyFunction2() returns boolean { boolean testPassed = true; - Employee e1 = {name: "Frank", address: {unitNo: 111, street: "Main Street"}, tokens: {one:1, two:2, three:3}, - noOfShifts: [1, 2, 3]}; - Employee e2 = {name: "James", address: {unitNo: 222, street: "Main Street"}, tokens: {one:11, two:2, three:3}, - noOfShifts: [1, 2, 3]}; - Employee e3 = {name: "James", address: {unitNo: 222, street: "Cross Street"}, tokens: {one:111, two:2, three:3}, - noOfShifts: [1, 2, 3]}; - Employee e4 = {name: "Frank", address: {unitNo: 111, street: "Cross Street"}, tokens: {one:1111, two:2, three:3}, - noOfShifts: [1, 2, 3]}; - Employee e5 = {name: "Frank", address: {unitNo: 111, street: "Cross Street"}, tokens: {one:1111, two:2, three:3}, - noOfShifts: [3, 2, 3]}; + Employee e1 = { + name: "Frank", + address: {unitNo: 111, street: "Main Street"}, + tokens: {one: 1, two: 2, three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e2 = { + name: "James", + address: {unitNo: 222, street: "Main Street"}, + tokens: {one: 11, two: 2, three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e3 = { + name: "James", + address: {unitNo: 222, street: "Cross Street"}, + tokens: {one: 111, two: 2, three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e4 = { + name: "Frank", + address: {unitNo: 111, street: "Cross Street"}, + tokens: {one: 1111, two: 2, three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e5 = { + name: "Frank", + address: {unitNo: 111, street: "Cross Street"}, + tokens: {one: 1111, two: 2, three: 3}, + noOfShifts: [3, 2, 3] + }; Employee[] empList = [e1, e2, e3, e4, e5]; @@ -316,36 +352,36 @@ function testQueryExprWithOrderByClauseHavingUserDefinedOrderKeyFunction2() retu order by emp.name, emp.tokens["one"] descending, emp.noOfShifts[0] descending select emp; - testPassed = testPassed && opEmpList[0] == empList[4]; - testPassed = testPassed && opEmpList[1] == empList[3]; - testPassed = testPassed && opEmpList[2] == empList[0]; - testPassed = testPassed && opEmpList[3] == empList[2]; - testPassed = testPassed && opEmpList[4] == empList[1]; + testPassed = testPassed && opEmpList[0] == empList[4]; + testPassed = testPassed && opEmpList[1] == empList[3]; + testPassed = testPassed && opEmpList[2] == empList[0]; + testPassed = testPassed && opEmpList[3] == empList[2]; + testPassed = testPassed && opEmpList[4] == empList[1]; return testPassed; } function testQueryExprWithOrderByClauseHavingUserDefinedOrderKeyFunction3() returns CustomerProfile[] { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "James", noOfItems: 25}; - Customer c4 = {id: 4, name: "James", noOfItems: 25}; + CustomerPos c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerPos c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerPos c3 = {id: 3, name: "James", noOfItems: 25}; + CustomerPos c4 = {id: 4, name: "James", noOfItems: 25}; - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonPos p1 = {firstName: "Amy", lastName: "Melina", age: 23}; + PersonPos p2 = {firstName: "Frank", lastName: "James", age: 30}; - Customer[] customerList = [c1, c2, c3, c4]; - Person[] personList = [p1, p2]; + CustomerPos[] customerList = [c1, c2, c3, c4]; + PersonPos[] personList = [p1, p2]; CustomerProfile[] customerProfileList = from var customer in customerList - join var person in personList - on customer.name equals person.lastName - order by incrementCount(0), customer.noOfItems descending - select { - name: person.firstName, - age : person.age, - noOfItems: customer.noOfItems - }; + join var person in personList + on customer.name equals person.lastName + order by incrementCount(0), customer.noOfItems descending + select { + name: person.firstName, + age: person.age, + noOfItems: customer.noOfItems + }; return customerProfileList; } @@ -353,20 +389,56 @@ function testQueryExprWithOrderByClauseHavingUserDefinedOrderKeyFunction3() retu function testQueryExprWithOrderByClauseHavingNaNNilValues() returns boolean { boolean testPassed = true; - Employee e1 = {name: "Frank", address: {unitNo: 111, street: "Main Street"}, tokens: {one:1, two:2, three:3}, - noOfShifts: [1, 2, 3]}; - Employee e2 = {name: "James", address: {unitNo: 222, street: "Main Street"}, tokens: {one:11, two:(), three:3}, - noOfShifts: [1, 2, 3]}; - Employee e3 = {name: "James", address: {unitNo: 222, street: "Cross Street"}, tokens: {one:11, two:(0.0/0.0), - three:3}, noOfShifts: [1, 2, 3]}; - Employee e4 = {name: "Frank", address: {unitNo: 111, street: "Cross Street"}, tokens: {one:11, two:4, three:3}, - noOfShifts: [1, 2, 3]}; - Employee e5 = {name: "Frank", address: {unitNo: 111, street: "Cross Street"}, tokens: {one:11, two:4, three:()}, - noOfShifts: [1, 2, 3]}; - Employee e6 = {name: "Frank", address: {unitNo: 111, street: "Cross Street"}, tokens: {one:11, two:4, - three:(0.0/0.0)}, noOfShifts: [1, 2, 3]}; - Employee e7 = {name: "Frank", address: {unitNo: 111, street: "Cross Street"}, tokens: {one:11, two:4, three:55}, - noOfShifts: [1, 2, 3]}; + Employee e1 = { + name: "Frank", + address: {unitNo: 111, street: "Main Street"}, + tokens: {one: 1, two: 2, three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e2 = { + name: "James", + address: {unitNo: 222, street: "Main Street"}, + tokens: {one: 11, two: (), three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e3 = { + name: "James", + address: {unitNo: 222, street: "Cross Street"}, + tokens: { + one: 11, + two: (0.0 / 0.0), + three: 3 + }, + noOfShifts: [1, 2, 3] + }; + Employee e4 = { + name: "Frank", + address: {unitNo: 111, street: "Cross Street"}, + tokens: {one: 11, two: 4, three: 3}, + noOfShifts: [1, 2, 3] + }; + Employee e5 = { + name: "Frank", + address: {unitNo: 111, street: "Cross Street"}, + tokens: {one: 11, two: 4, three: ()}, + noOfShifts: [1, 2, 3] + }; + Employee e6 = { + name: "Frank", + address: {unitNo: 111, street: "Cross Street"}, + tokens: { + one: 11, + two: 4, + three: (0.0 / 0.0) + }, + noOfShifts: [1, 2, 3] + }; + Employee e7 = { + name: "Frank", + address: {unitNo: 111, street: "Cross Street"}, + tokens: {one: 11, two: 4, three: 55}, + noOfShifts: [1, 2, 3] + }; Employee[] empList = [e1, e2, e3, e4, e5, e6, e7]; @@ -374,30 +446,30 @@ function testQueryExprWithOrderByClauseHavingNaNNilValues() returns boolean { order by emp.tokens["two"] descending, emp.tokens["three"] ascending select emp; - testPassed = testPassed && opEmpList[0] == empList[3]; - testPassed = testPassed && opEmpList[1] == empList[6]; - testPassed = testPassed && opEmpList[2] == empList[5]; - testPassed = testPassed && opEmpList[3] == empList[4]; - testPassed = testPassed && opEmpList[4] == empList[0]; - testPassed = testPassed && opEmpList[5] == empList[2]; - testPassed = testPassed && opEmpList[6] == empList[1]; + testPassed = testPassed && opEmpList[0] == empList[3]; + testPassed = testPassed && opEmpList[1] == empList[6]; + testPassed = testPassed && opEmpList[2] == empList[5]; + testPassed = testPassed && opEmpList[3] == empList[4]; + testPassed = testPassed && opEmpList[4] == empList[0]; + testPassed = testPassed && opEmpList[5] == empList[2]; + testPassed = testPassed && opEmpList[6] == empList[1]; return testPassed; } function testQueryExprWithOrderByClauseReturnString() returns string { - Person p1 = {firstName: "Amy", lastName: "Melina", age: 34}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; - Person p3 = {firstName: "Melina", lastName: "Kodel", age: 72}; - Person p4 = {firstName: "Terrence", lastName: "Lewis", age: 19}; - Person p5 = {firstName: "Meghan", lastName: "Markle", age: 55}; + PersonPos p1 = {firstName: "Amy", lastName: "Melina", age: 34}; + PersonPos p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonPos p3 = {firstName: "Melina", lastName: "Kodel", age: 72}; + PersonPos p4 = {firstName: "Terrence", lastName: "Lewis", age: 19}; + PersonPos p5 = {firstName: "Meghan", lastName: "Markle", age: 55}; - Person[] personList = [p1, p2, p3, p4, p5]; + PersonPos[] personList = [p1, p2, p3, p4, p5]; string outputNameString = from var person in personList - order by person.age descending - limit 3 - select person.firstName+" "+person.lastName+","; + order by person.age descending + limit 3 + select person.firstName + " " + person.lastName + ","; return outputNameString; } @@ -419,53 +491,58 @@ function testQueryExprWithOrderByClauseReturnXML() returns xml { `; xml authors = from var book in bookStore// - order by book.toString() - limit 2 - select book; + order by book.toString() + limit 2 + select book; - return authors; + return authors; } function testQueryExprWithOrderByClauseAndInnerQueries() returns CustomerProfile[] { - Customer c1 = {id: 1, name: "Melina", noOfItems: 62}; - Customer c2 = {id: 5, name: "James", noOfItems: 5}; - Customer c3 = {id: 9, name: "James", noOfItems: 25}; - Customer c4 = {id: 0, name: "James", noOfItems: 25}; - Customer c5 = {id: 2, name: "James", noOfItems: 30}; + CustomerPos c1 = {id: 1, name: "Melina", noOfItems: 62}; + CustomerPos c2 = {id: 5, name: "James", noOfItems: 5}; + CustomerPos c3 = {id: 9, name: "James", noOfItems: 25}; + CustomerPos c4 = {id: 0, name: "James", noOfItems: 25}; + CustomerPos c5 = {id: 2, name: "James", noOfItems: 30}; - Person p1 = {firstName: "Jennifer", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; - Person p3 = {firstName: "Zeth", lastName: "James", age: 50}; + PersonPos p1 = {firstName: "Jennifer", lastName: "Melina", age: 23}; + PersonPos p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonPos p3 = {firstName: "Zeth", lastName: "James", age: 50}; - Customer[] customerList = [c1, c2, c3, c4, c5]; - Person[] personList = [p1, p2, p3]; + CustomerPos[] customerList = [c1, c2, c3, c4, c5]; + PersonPos[] personList = [p1, p2, p3]; CustomerProfile[] customerProfileList = from var customer in (stream from var c in customerList - order by c.id descending limit 4 select c) - join var person in (from var p in personList order by p.firstName descending limit 2 select p) - on customer.name equals person.lastName - order by incrementCount(0), customer.noOfItems descending - limit 3 - select { - name: person.firstName, - age : person.age, - noOfItems: customer.noOfItems - }; + order by c.id descending + limit 4 + select c) + join var person in (from var p in personList + order by p.firstName descending + limit 2 + select p) + on customer.name equals person.lastName + order by incrementCount(0), customer.noOfItems descending + limit 3 + select { + name: person.firstName, + age: person.age, + noOfItems: customer.noOfItems + }; return customerProfileList; } function testQueryExprWithOrderByClauseAndInnerQueries2() returns CustomerProfile[] { - Customer c1 = {id: 1, name: "Melina", noOfItems: 62}; - Customer c2 = {id: 5, name: "James", noOfItems: 5}; - Customer c3 = {id: 9, name: "James", noOfItems: 25}; - Customer c4 = {id: 0, name: "James", noOfItems: 25}; - Customer c5 = {id: 2, name: "James", noOfItems: 30}; - Customer c6 = {id: 3, name: "Melina", noOfItems: 20}; + CustomerPos c1 = {id: 1, name: "Melina", noOfItems: 62}; + CustomerPos c2 = {id: 5, name: "James", noOfItems: 5}; + CustomerPos c3 = {id: 9, name: "James", noOfItems: 25}; + CustomerPos c4 = {id: 0, name: "James", noOfItems: 25}; + CustomerPos c5 = {id: 2, name: "James", noOfItems: 30}; + CustomerPos c6 = {id: 3, name: "Melina", noOfItems: 20}; - Person p1 = {firstName: "Jennifer", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; - Person p3 = {firstName: "Zeth", lastName: "James", age: 50}; + PersonPos p1 = {firstName: "Jennifer", lastName: "Melina", age: 23}; + PersonPos p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonPos p3 = {firstName: "Zeth", lastName: "James", age: 50}; PaymentInfo i1 = {custId: 1, modeOfPayment: "cash"}; PaymentInfo i2 = {custId: 9, modeOfPayment: "debit card"}; @@ -476,8 +553,8 @@ function testQueryExprWithOrderByClauseAndInnerQueries2() returns CustomerProfil PaymentInfo i7 = {custId: 2, modeOfPayment: "cash"}; PaymentInfo i8 = {custId: 3, modeOfPayment: "cash"}; - Customer[] customerList = [c1, c2, c3, c4, c5, c6]; - Person[] personList = [p1, p2, p3]; + CustomerPos[] customerList = [c1, c2, c3, c4, c5, c6]; + PersonPos[] personList = [p1, p2, p3]; PaymentInfo[] paymentList = [i1, i2, i3, i4, i5, i6, i7, i8]; CustomerProfile[] customerProfileList = from var customer in (stream from var c in customerList diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/query/query-expr-with-query-construct-type.bal b/tests/jballerina-unit-test/src/test/resources/test-src/query/query-expr-with-query-construct-type.bal index 3cf3bc40fafe..0145356cd7ca 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/query/query-expr-with-query-construct-type.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/query/query-expr-with-query-construct-type.bal @@ -14,23 +14,23 @@ // specific language governing permissions and limitations // under the License. -type Person record {| +type PersonX record {| string firstName; string lastName; int age; |}; -type Employee record {| +type EmployeeX record {| string firstName; string lastName; string dept; |}; -type Department record { +type DepartmentX record { string dept; }; -type EmpProfile record {| +type EmpProfileX record {| string firstName; string lastName; int age; @@ -38,67 +38,67 @@ type EmpProfile record {| string status; |}; -type PersonValue record {| - Person value; +type PersonValueX record {| + PersonX value; |}; -type EmployeeValue record {| - Employee value; +type EmployeeValueX record {| + EmployeeX value; |}; -type EmpProfileValue record {| - EmpProfile value; +type EmpProfileValueX record {| + EmpProfileX value; |}; -type Customer record {| +type CustomerX record {| readonly int id; readonly string name; int noOfItems; |}; -type CustomerTable table key(id, name); +type CustomerTableX table key(id, name); -type CustomerKeyLessTable table; +type CustomerKeyLessTableX table; -type CustomerValue record {| - Customer value; +type CustomerValueX record {| + CustomerX value; |}; -function getPersonValue((record {| Person value; |}|error?)|(record {| Person value; |}?) returnedVal) -returns PersonValue? { +function getPersonValue((record {|PersonX value;|}|error?)|(record {|PersonX value;|}?) returnedVal) +returns PersonValueX? { var result = returnedVal; - if (result is PersonValue) { + if (result is PersonValueX) { return result; } else { return (); } } -function getEmployeeValue((record {| Employee value; |}|error?)|(record {| Employee value; |}?) returnedVal) -returns EmployeeValue? { +function getEmployeeValue((record {|EmployeeX value;|}|error?)|(record {|EmployeeX value;|}?) returnedVal) +returns EmployeeValueX? { var result = returnedVal; - if (result is EmployeeValue) { + if (result is EmployeeValueX) { return result; } else { return (); } } -function getEmpProfileValue((record {| EmpProfile value; |}|error?)|(record {| EmpProfile value; |}?) returnedVal) -returns EmpProfileValue? { +function getEmpProfileValue((record {|EmpProfileX value;|}|error?)|(record {|EmpProfileX value;|}?) returnedVal) +returns EmpProfileValueX? { var result = returnedVal; - if (result is EmpProfileValue) { + if (result is EmpProfileValueX) { return result; } else { return (); } } -function getCustomer(record {| Customer value; |}? returnedVal) returns Customer? { - if (returnedVal is CustomerValue) { - return returnedVal.value; +function getCustomer(record {|CustomerX value;|}? returnedVal) returns CustomerX? { + if (returnedVal is CustomerValueX) { + return returnedVal.value; } else { - return (); + return (); } } @@ -107,13 +107,13 @@ function getCustomer(record {| Customer value; |}? returnedVal) returns Customer function testSimpleQueryReturnStream() returns boolean { boolean testPassed = true; - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonX p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonX p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonX p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonX[] personList = [p1, p2, p3]; - stream outputPersonStream = stream from var person in personList + stream outputPersonStream = stream from var person in personList where person.firstName == "John" let int newAge = 34 select { @@ -122,7 +122,7 @@ function testSimpleQueryReturnStream() returns boolean { age: newAge }; - record {| Person value; |}? person = getPersonValue(outputPersonStream.next()); + record {|PersonX value;|}? person = getPersonValue(outputPersonStream.next()); testPassed = testPassed && person?.value?.firstName == "John" && person?.value?.lastName == "David" && person?.value?.age == 34; @@ -135,11 +135,11 @@ function testSimpleQueryReturnStream() returns boolean { function testSimpleQueryReturnStream2() { boolean testPassed = true; - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonX p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonX p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonX p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonX[] personList = [p1, p2, p3]; var outputPersonStream = stream from var person in personList where person.firstName == "John" @@ -150,10 +150,10 @@ function testSimpleQueryReturnStream2() { age: newAge }; - assertTrue(outputPersonStream is stream); - stream _ = outputPersonStream; + assertTrue(outputPersonStream is stream); + stream _ = outputPersonStream; - record {| Person value; |}? person = getPersonValue(outputPersonStream.next()); + record {|PersonX value;|}? person = getPersonValue(outputPersonStream.next()); testPassed = testPassed && person?.value?.firstName == "John" && person?.value?.lastName == "David" && person?.value?.age == 34; @@ -163,37 +163,39 @@ function testSimpleQueryReturnStream2() { assertTrue(testPassed); } -type ValueRecord record {| +type ValueRecordX record {| string value; |}; -type TestStream stream; +type TestStreamX stream; -class TestGenerator { - public isolated function next() returns ValueRecord|error? { +class TestGeneratorX { + public isolated function next() returns ValueRecordX|error? { return {value: "Ballerina"}; } } function testSimpleQueryReturnStream3() { - TestGenerator generator = new (); - TestStream testStream = new (generator); + TestGeneratorX generator = new (); + TestStreamX testStream = new (generator); - var outputIntPersonStream = stream from var _ in testStream select 1; + var outputIntPersonStream = stream from var _ in testStream + select 1; assertTrue(outputIntPersonStream is stream); stream _ = outputIntPersonStream; - (record {| int value; |}|error)? x1 = outputIntPersonStream.next(); - if (x1 is record {| int value; |}) { + (record {|int value;|}|error)? x1 = outputIntPersonStream.next(); + if (x1 is record {|int value;|}) { assertEqual(x1.value, 1); } else { assertTrue(false); } - var outputStringPersonStream = stream from var _ in testStream select "ABCD"; + var outputStringPersonStream = stream from var _ in testStream + select "ABCD"; assertTrue(outputStringPersonStream is stream); stream _ = outputStringPersonStream; - (record {| string value; |}|error)? x2 = outputStringPersonStream.next(); - if (x2 is record {| string value; |}) { + (record {|string value;|}|error)? x2 = outputStringPersonStream.next(); + if (x2 is record {|string value;|}) { assertEqual(x2.value, "ABCD"); } else { assertTrue(false); @@ -203,30 +205,30 @@ function testSimpleQueryReturnStream3() { function testStreamInFromClauseWithReturnStream() returns boolean { boolean testPassed = true; - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; - - Person[] personList = [p1, p2, p3]; - - stream outputEmployeeStream = stream from var {firstName, lastName, dept} in - >personList.toStream().filter(function (Person person) returns boolean { - return person.firstName == "John"; - }).'map(function (Person person) returns Employee { - Employee employee = { - firstName: person.firstName, - lastName: person.lastName, - dept: "Engineering" - }; - return employee; - }) - select { - firstName: firstName, - lastName: lastName, - dept: dept - }; - - record {| Employee value; |}? employee = getEmployeeValue(outputEmployeeStream.next()); + PersonX p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonX p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonX p3 = {firstName: "John", lastName: "David", age: 33}; + + PersonX[] personList = [p1, p2, p3]; + + stream outputEmployeeStream = stream from var {firstName, lastName, dept} in + >personList.toStream().filter(function(PersonX person) returns boolean { + return person.firstName == "John"; + }).'map(function(PersonX person) returns EmployeeX { + EmployeeX employee = { + firstName: person.firstName, + lastName: person.lastName, + dept: "Engineering" + }; + return employee; + }) + select { + firstName: firstName, + lastName: lastName, + dept: dept + }; + + record {|EmployeeX value;|}? employee = getEmployeeValue(outputEmployeeStream.next()); testPassed = testPassed && employee?.value?.firstName == "John" && employee?.value?.lastName == "David" && employee?.value?.dept == "Engineering"; @@ -239,33 +241,33 @@ function testStreamInFromClauseWithReturnStream() returns boolean { function testStreamInFromClauseWithReturnStream2() { boolean testPassed = true; - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonX p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonX p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonX p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonX[] personList = [p1, p2, p3]; var outputEmployeeStream = stream from var {firstName, lastName, dept} in - >personList.toStream().filter(function (Person person) returns boolean { - return person.firstName == "John"; - }).'map(function (Person person) returns Employee { - Employee employee = { - firstName: person.firstName, - lastName: person.lastName, - dept: "Engineering" - }; - return employee; - }) - select { - firstName: firstName, - lastName: lastName, - dept: dept - }; - - assertTrue(outputEmployeeStream is stream); - stream _ = outputEmployeeStream; - - record {| Employee value; |}? employee = getEmployeeValue(outputEmployeeStream.next()); + >personList.toStream().filter(function(PersonX person) returns boolean { + return person.firstName == "John"; + }).'map(function(PersonX person) returns EmployeeX { + EmployeeX employee = { + firstName: person.firstName, + lastName: person.lastName, + dept: "Engineering" + }; + return employee; + }) + select { + firstName: firstName, + lastName: lastName, + dept: dept + }; + + assertTrue(outputEmployeeStream is stream); + stream _ = outputEmployeeStream; + + record {|EmployeeX value;|}? employee = getEmployeeValue(outputEmployeeStream.next()); testPassed = testPassed && employee?.value?.firstName == "John" && employee?.value?.lastName == "David" && employee?.value?.dept == "Engineering"; @@ -277,28 +279,28 @@ function testStreamInFromClauseWithReturnStream2() { function testMultipleFromWhereAndLetReturnStream() returns boolean { boolean testPassed = true; - Employee e1 = {firstName: "John", lastName: "Fonseka", dept: "Engineering"}; - Employee e2 = {firstName: "John", lastName: "David", dept: "HR"}; + EmployeeX e1 = {firstName: "John", lastName: "Fonseka", dept: "Engineering"}; + EmployeeX e2 = {firstName: "John", lastName: "David", dept: "HR"}; - Department d1 = {dept: "Support"}; - Department d2 = {dept: "Dev"}; + DepartmentX d1 = {dept: "Support"}; + DepartmentX d2 = {dept: "Dev"}; - Employee[] employeeList = [e1, e2]; - Department[] departmentList = [d1, d2]; + EmployeeX[] employeeList = [e1, e2]; + DepartmentX[] departmentList = [d1, d2]; - stream outputEmployeeStream = stream from var emp in employeeList - from var department in departmentList - where emp.firstName == "John" - where emp.dept == "Engineering" - let string fname = "Johns" - let string deptName = "Research" - select { - firstName: fname, - lastName: emp.lastName, - dept: deptName - }; + stream outputEmployeeStream = stream from var emp in employeeList + from var department in departmentList + where emp.firstName == "John" + where emp.dept == "Engineering" + let string fname = "Johns" + let string deptName = "Research" + select { + firstName: fname, + lastName: emp.lastName, + dept: deptName + }; - record {| Employee value; |}? employee = getEmployeeValue(outputEmployeeStream.next()); + record {|EmployeeX value;|}? employee = getEmployeeValue(outputEmployeeStream.next()); testPassed = testPassed && employee?.value?.firstName == "Johns" && employee?.value?.lastName == "Fonseka" && employee?.value?.dept == "Research"; @@ -315,31 +317,31 @@ function testMultipleFromWhereAndLetReturnStream() returns boolean { function testMultipleFromWhereAndLetReturnStream2() { boolean testPassed = true; - Employee e1 = {firstName: "John", lastName: "Fonseka", dept: "Engineering"}; - Employee e2 = {firstName: "John", lastName: "David", dept: "HR"}; + EmployeeX e1 = {firstName: "John", lastName: "Fonseka", dept: "Engineering"}; + EmployeeX e2 = {firstName: "John", lastName: "David", dept: "HR"}; - Department d1 = {dept: "Support"}; - Department d2 = {dept: "Dev"}; + DepartmentX d1 = {dept: "Support"}; + DepartmentX d2 = {dept: "Dev"}; - Employee[] employeeList = [e1, e2]; - Department[] departmentList = [d1, d2]; + EmployeeX[] employeeList = [e1, e2]; + DepartmentX[] departmentList = [d1, d2]; var outputEmployeeStream = stream from var emp in employeeList - from var department in departmentList - where emp.firstName == "John" - where emp.dept == "Engineering" - let string fname = "Johns" - let string deptName = "Research" - select { - firstName: fname, - lastName: emp.lastName, - dept: deptName - }; - - assertTrue(outputEmployeeStream is stream); - stream _ = outputEmployeeStream; - - record {| Employee value; |}? employee = getEmployeeValue(outputEmployeeStream.next()); + from var department in departmentList + where emp.firstName == "John" + where emp.dept == "Engineering" + let string fname = "Johns" + let string deptName = "Research" + select { + firstName: fname, + lastName: emp.lastName, + dept: deptName + }; + + assertTrue(outputEmployeeStream is stream); + stream _ = outputEmployeeStream; + + record {|EmployeeX value;|}? employee = getEmployeeValue(outputEmployeeStream.next()); testPassed = testPassed && employee?.value?.firstName == "Johns" && employee?.value?.lastName == "Fonseka" && employee?.value?.dept == "Research"; @@ -352,85 +354,90 @@ function testMultipleFromWhereAndLetReturnStream2() { assertTrue(testPassed); } -type Employee2 record { +type Employee2X record { readonly string name; int salary; }; -type Tbl table key(name); +type TblX table key(name); function testConstructTablesWithRecords() { - table key(name) t = table [ - { name: "John", salary: 100 }, - { name: "Jane", salary: 200 } + table key(name) t = table [ + {name: "John", salary: 100}, + {name: "Jane", salary: 200} ]; - var ct = from Employee2 e in t select e; - assertTrue(ct is table); - table a = ct; + var ct = from Employee2X e in t + select e; + assertTrue(ct is table); + table a = ct; assertEqual(a.toString(), "[{\"name\":\"John\",\"salary\":100},{\"name\":\"Jane\",\"salary\":200}]"); - table key(name) t2 = table [ - { name: "John", salary: 100 }, - { name: "Jane", salary: 200 } - ]; + table key(name) t2 = table [ + {name: "John", salary: 100}, + {name: "Jane", salary: 200} + ]; - var ct2 = from record { readonly string name; int salary; } e in t2 select e; - assertTrue(ct2 is table); - table a2 = ct2; + var ct2 = from record {readonly string name; int salary;} e in t2 + select e; + assertTrue(ct2 is table); + table a2 = ct2; assertEqual(a2.toString(), "[{\"name\":\"John\",\"salary\":100},{\"name\":\"Jane\",\"salary\":200}]"); - var ct3 = from Employee2 e in t select {name: e.name}; + var ct3 = from Employee2X e in t + select {name: e.name}; assertTrue(ct3 is table); table a3 = ct3; assertEqual(a3.toString(), "[{\"name\":\"John\"},{\"name\":\"Jane\"}]"); - Tbl t3 = table [ - { name: "John", salary: 100 }, - { name: "Jane", salary: 200 } + TblX t3 = table [ + {name: "John", salary: 100}, + {name: "Jane", salary: 200} ]; - var ct4 = from Employee2 e in t3 select e; - assertTrue(ct4 is table); - table a4 = ct4; + var ct4 = from Employee2X e in t3 + select e; + assertTrue(ct4 is table); + table a4 = ct4; assertEqual(a4.toString(), "[{\"name\":\"John\",\"salary\":100},{\"name\":\"Jane\",\"salary\":200}]"); } function testConstructMapsWithTuples() { map a = {"a": 1, "b": 2}; - var cm = map from var i in a select ["A",1]; - assertTrue(cm is map); - map cm2 = cm; - assertEqual(cm2, {"A": 1}); + var cm = map from var i in a + select ["A", 1]; + assertTrue(cm is map); + map cm2 = cm; + assertEqual(cm2, {"A": 1}); } function testInnerJoinAndLimitReturnStream() returns boolean { boolean testPassed = true; - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonX p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonX p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Employee e1 = {firstName: "Alex", lastName: "George", dept: "Engineering"}; - Employee e2 = {firstName: "John", lastName: "David", dept: "HR"}; - Employee e3 = {firstName: "Ranjan", lastName: "Fonseka", dept: "Operations"}; + EmployeeX e1 = {firstName: "Alex", lastName: "George", dept: "Engineering"}; + EmployeeX e2 = {firstName: "John", lastName: "David", dept: "HR"}; + EmployeeX e3 = {firstName: "Ranjan", lastName: "Fonseka", dept: "Operations"}; - Person[] personList = [p1, p2]; - Employee[] employeeList = [e1, e2, e3]; + PersonX[] personList = [p1, p2]; + EmployeeX[] employeeList = [e1, e2, e3]; - stream outputEmpProfileStream = stream from var person in personList.toStream() - join Employee employee in employeeList.toStream() + stream outputEmpProfileStream = stream from var person in personList.toStream() + join EmployeeX employee in employeeList.toStream() on person.firstName equals employee.firstName - limit 1 - select { - firstName: employee.firstName, - lastName: employee.lastName, - age: person.age, - dept: employee.dept, - status: "Permanent" - }; + limit 1 + select { + firstName: employee.firstName, + lastName: employee.lastName, + age: person.age, + dept: employee.dept, + status: "Permanent" + }; - record {| EmpProfile value; |}? empProfile = getEmpProfileValue(outputEmpProfileStream.next()); + record {|EmpProfileX value;|}? empProfile = getEmpProfileValue(outputEmpProfileStream.next()); testPassed = testPassed && empProfile?.value?.firstName == "Alex" && empProfile?.value?.lastName == "George" && empProfile?.value?.age == 23 && empProfile?.value?.dept == "Engineering" && empProfile?.value?.status == "Permanent"; @@ -444,32 +451,32 @@ function testInnerJoinAndLimitReturnStream() returns boolean { function testInnerJoinAndLimitReturnStream2() { boolean testPassed = true; - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonX p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonX p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Employee e1 = {firstName: "Alex", lastName: "George", dept: "Engineering"}; - Employee e2 = {firstName: "John", lastName: "David", dept: "HR"}; - Employee e3 = {firstName: "Ranjan", lastName: "Fonseka", dept: "Operations"}; + EmployeeX e1 = {firstName: "Alex", lastName: "George", dept: "Engineering"}; + EmployeeX e2 = {firstName: "John", lastName: "David", dept: "HR"}; + EmployeeX e3 = {firstName: "Ranjan", lastName: "Fonseka", dept: "Operations"}; - Person[] personList = [p1, p2]; - Employee[] employeeList = [e1, e2, e3]; + PersonX[] personList = [p1, p2]; + EmployeeX[] employeeList = [e1, e2, e3]; var outputEmpProfileStream = stream from var person in personList.toStream() - join Employee employee in employeeList.toStream() + join EmployeeX employee in employeeList.toStream() on person.firstName equals employee.firstName - limit 1 - select { - firstName: employee.firstName, - lastName: employee.lastName, - age: person.age, - dept: employee.dept, - status: "Permanent" - }; + limit 1 + select { + firstName: employee.firstName, + lastName: employee.lastName, + age: person.age, + dept: employee.dept, + status: "Permanent" + }; - assertTrue(outputEmpProfileStream is stream); - stream _ = outputEmpProfileStream; + assertTrue(outputEmpProfileStream is stream); + stream _ = outputEmpProfileStream; - record {| EmpProfile value; |}? empProfile = getEmpProfileValue(outputEmpProfileStream.next()); + record {|EmpProfileX value;|}? empProfile = getEmpProfileValue(outputEmpProfileStream.next()); testPassed = testPassed && empProfile?.value?.firstName == "Alex" && empProfile?.value?.lastName == "George" && empProfile?.value?.age == 23 && empProfile?.value?.dept == "Engineering" && empProfile?.value?.status == "Permanent"; @@ -484,22 +491,22 @@ function testInnerJoinAndLimitReturnStream2() { function testSimpleQueryExprReturnTable() returns boolean { boolean testPassed = true; - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList = [c1, c2, c3]; + CustomerX[] customerList = [c1, c2, c3]; - CustomerTable customerTable = table key(id, name) from var customer in customerList + CustomerTableX customerTable = table key(id, name) from var customer in customerList select { id: customer.id, name: customer.name, noOfItems: customer.noOfItems }; - if (customerTable is CustomerTable) { + if (customerTable is CustomerTableX) { var itr = customerTable.iterator(); - Customer? customer = getCustomer(itr.next()); + CustomerX? customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[0]; customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[1]; @@ -515,11 +522,11 @@ function testSimpleQueryExprReturnTable() returns boolean { function testSimpleQueryExprReturnTable2() { boolean testPassed = true; - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList = [c1, c2, c3]; + CustomerX[] customerList = [c1, c2, c3]; var customerTable = table key(id, name) from var customer in customerList select { @@ -528,12 +535,12 @@ function testSimpleQueryExprReturnTable2() { noOfItems: customer.noOfItems }; - assertTrue(customerTable is CustomerTable); - CustomerTable _ = customerTable; + assertTrue(customerTable is CustomerTableX); + CustomerTableX _ = customerTable; - if (customerTable is CustomerTable) { + if (customerTable is CustomerTableX) { var itr = customerTable.iterator(); - Customer? customer = getCustomer(itr.next()); + CustomerX? customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[0]; customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[1]; @@ -547,26 +554,29 @@ function testSimpleQueryExprReturnTable2() { } function testTableWithDuplicateKeys() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; - Customer[] customerList = [c1, c2, c1]; + CustomerX[] customerList = [c1, c2, c1]; - CustomerTable customerTable = table key(id, name) from var customer in customerList + CustomerTableX customerTable = table key(id, name) from var customer in customerList select { id: customer.id, name: customer.name, noOfItems: customer.noOfItems }; - assertEqual(customerTable, table key(id,name) [{"id":1,"name":"Melina","noOfItems":12},{"id":2,"name":"James","noOfItems":5}]); + assertEqual(customerTable, table key(id, name) [ + {"id": 1, "name": "Melina", "noOfItems": 12}, + {"id": 2, "name": "James", "noOfItems": 5} + ]); } function testTableWithDuplicateKeys2() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; - Customer[] customerList = [c1, c2, c1]; + CustomerX[] customerList = [c1, c2, c1]; var customerTable = table key(id, name) from var customer in customerList select { @@ -575,23 +585,26 @@ function testTableWithDuplicateKeys2() { noOfItems: customer.noOfItems }; - assertTrue(customerTable is CustomerTable); - CustomerTable _ = customerTable; + assertTrue(customerTable is CustomerTableX); + CustomerTableX _ = customerTable; - assertEqual(customerTable, table key(id,name) [{"id":1,"name":"Melina","noOfItems":12},{"id":2,"name":"James","noOfItems":5}]); + assertEqual(customerTable, table key(id, name) [ + {"id": 1, "name": "Melina", "noOfItems": 12}, + {"id": 2, "name": "James", "noOfItems": 5} + ]); } function testTableNoDuplicatesAndOnConflictReturnTable() returns boolean { boolean testPassed = true; error onConflictError = error("Key Conflict", message = "cannot insert."); - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList = [c1, c2, c3]; + CustomerX[] customerList = [c1, c2, c3]; - CustomerTable|error customerTable = table key(id, name) from var customer in customerList + CustomerTableX|error customerTable = table key(id, name) from var customer in customerList select { id: customer.id, name: customer.name, @@ -599,9 +612,9 @@ function testTableNoDuplicatesAndOnConflictReturnTable() returns boolean { } on conflict onConflictError; - if (customerTable is CustomerTable) { + if (customerTable is CustomerTableX) { var itr = customerTable.iterator(); - Customer? customer = getCustomer(itr.next()); + CustomerX? customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[0]; customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[1]; @@ -615,113 +628,113 @@ function testTableNoDuplicatesAndOnConflictReturnTable() returns boolean { } function testTableWithDuplicatesAndOnConflictReturnTable() { - error onConflictError = error("Key Conflict", message = "cannot insert."); + error onConflictError = error("Key Conflict", message = "cannot insert."); - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; - Customer[] customerList = [c1, c2, c1]; + CustomerX[] customerList = [c1, c2, c1]; - CustomerTable|error customerTable = table key(id, name) from var customer in customerList - select { - id: customer.id, - name: customer.name, - noOfItems: customer.noOfItems - } - on conflict onConflictError; + CustomerTableX|error customerTable = table key(id, name) from var customer in customerList + select { + id: customer.id, + name: customer.name, + noOfItems: customer.noOfItems + } + on conflict onConflictError; - validateKeyConflictError(customerTable); + validateKeyConflictError(customerTable); } function testQueryExprWithOtherClausesReturnTable() { - error onConflictError = error("Key Conflict", message = "cannot insert."); + error onConflictError = error("Key Conflict", message = "cannot insert."); - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonX p1 = {firstName: "Amy", lastName: "Melina", age: 23}; + PersonX p2 = {firstName: "Frank", lastName: "James", age: 30}; - Customer[] customerList = [c1, c2, c1]; - Person[] personList = [p1, p2]; + CustomerX[] customerList = [c1, c2, c1]; + PersonX[] personList = [p1, p2]; - CustomerTable|error customerTable = table key(id, name) from var customer in customerList - from var person in personList - let int items = 25 - let string customerName = "Bini" - where customer.id == 1 - where person.firstName == "Amy" - select { - id: customer.id, - name: customerName, - noOfItems: items - } - on conflict onConflictError; + CustomerTableX|error customerTable = table key(id, name) from var customer in customerList + from var person in personList + let int items = 25 + let string customerName = "Bini" + where customer.id == 1 + where person.firstName == "Amy" + select { + id: customer.id, + name: customerName, + noOfItems: items + } + on conflict onConflictError; - validateKeyConflictError(customerTable); + validateKeyConflictError(customerTable); } function validateKeyConflictError(any|error value) { - if (value is error) { - any|error detailMessage = value.detail()["message"]; - if (value.message() == "Key Conflict" + if (value is error) { + any|error detailMessage = value.detail()["message"]; + if (value.message() == "Key Conflict" && detailMessage is string && detailMessage == "cannot insert.") { - return; - } - panic error("Assertion error"); - } - panic error("Expected error, found: " + (typeof value).toString()); + return; + } + panic error("Assertion error"); + } + panic error("Expected error, found: " + (typeof value).toString()); } function testQueryExprWithJoinClauseReturnTable() { - error onConflictError = error("Key Conflict", message = "cannot insert."); + error onConflictError = error("Key Conflict", message = "cannot insert."); - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonX p1 = {firstName: "Amy", lastName: "Melina", age: 23}; + PersonX p2 = {firstName: "Frank", lastName: "James", age: 30}; - Customer[] customerList = [c1, c2, c1]; - Person[] personList = [p1, p2]; + CustomerX[] customerList = [c1, c2, c1]; + PersonX[] personList = [p1, p2]; - CustomerTable|error customerTable = table key(id, name) from var customer in customerList - join var person in personList - on customer.name equals person.lastName - select { - id: customer.id, - name: person.firstName, - noOfItems: customer.noOfItems - } - on conflict onConflictError; + CustomerTableX|error customerTable = table key(id, name) from var customer in customerList + join var person in personList + on customer.name equals person.lastName + select { + id: customer.id, + name: person.firstName, + noOfItems: customer.noOfItems + } + on conflict onConflictError; - validateKeyConflictError(customerTable); + validateKeyConflictError(customerTable); } function testQueryExprWithLimitClauseReturnTable() returns boolean { - boolean testPassed = true; - error onConflictError = error("Key Conflict", message = "cannot insert."); - - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Melina", noOfItems: 25}; - - Customer[] customerList = [c1, c2, c3]; - - CustomerTable|error customerTable = table key(id, name) from var customer in customerList.toStream() - where customer.name == "Melina" - limit 1 - select { - id: customer.id, - name: customer.name, - noOfItems: customer.noOfItems - } - on conflict onConflictError; - - if (customerTable is CustomerTable) { + boolean testPassed = true; + error onConflictError = error("Key Conflict", message = "cannot insert."); + + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Melina", noOfItems: 25}; + + CustomerX[] customerList = [c1, c2, c3]; + + CustomerTableX|error customerTable = table key(id, name) from var customer in customerList.toStream() + where customer.name == "Melina" + limit 1 + select { + id: customer.id, + name: customer.name, + noOfItems: customer.noOfItems + } + on conflict onConflictError; + + if (customerTable is CustomerTableX) { var itr = customerTable.iterator(); - Customer? customer = getCustomer(itr.next()); + CustomerX? customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[0]; customer = getCustomer(itr.next()); testPassed = testPassed && customer == (); @@ -733,22 +746,22 @@ function testQueryExprWithLimitClauseReturnTable() returns boolean { function testKeyLessTableWithReturnTable() returns boolean { boolean testPassed = true; - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList = [c1, c2, c3]; + CustomerX[] customerList = [c1, c2, c3]; - CustomerKeyLessTable customerTable = table key(id, name) from var customer in customerList + CustomerKeyLessTableX customerTable = table key(id, name) from var customer in customerList select { id: customer.id, name: customer.name, noOfItems: customer.noOfItems }; - if (customerTable is CustomerKeyLessTable) { + if (customerTable is CustomerKeyLessTableX) { var itr = customerTable.iterator(); - Customer? customer = getCustomer(itr.next()); + CustomerX? customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[0]; customer = getCustomer(itr.next()); testPassed = testPassed && customer == customerList[1]; @@ -761,7 +774,7 @@ function testKeyLessTableWithReturnTable() returns boolean { return testPassed; } -type User record { +type UserX record { readonly int id; string firstName; string lastName; @@ -769,67 +782,67 @@ type User record { }; function testQueryConstructingTableUpdateKeyPanic1() returns error? { - table key(id) users = table [ + table key(id) users = table [ {id: 1, firstName: "John", lastName: "Doe", age: 25} ]; var result = table key(id, name) from var user in users - where user.age > 21 && user.age < 60 - select {id: user.id, name: user.firstName, user}; + where user.age > 21 && user.age < 60 + select {id: user.id, name: user.firstName, user}; var r2 = result[1, "John"]; + UserX user; + }>result[1, "John"]; r2.id = 1; } -type NewUser record {| +type NewUserX record {| readonly int id; readonly string name; - User user; + UserX user; |}; function testQueryConstructingTableUpdateKeyPanic2() returns error? { - table key(id) users = table [ + table key(id) users = table [ {id: 1, firstName: "John", lastName: "Doe", age: 25} ]; - table key(id, name) result = - table key(id, name) from var user in users - where user.age > 21 && user.age < 60 - select {id: user.id, name: user.firstName, user}; + table key(id, name) result = + table key(id, name) from var user in users + where user.age > 21 && user.age < 60 + select {id: user.id, name: user.firstName, user}; var r2 = result[1, "John"]; + UserX user; + }>result[1, "John"]; r2.id = 2; } -type CustomErrorDetail record {| +type CustomErrorDetailX record {| string message; int code; |}; -type CustomError error; +type CustomErrorX error; function testTableOnConflict() { error? onConflictError1 = error("Key Conflict", message = "cannot insert."); error|null onConflictError2 = (); error|null onConflictError3 = null; - CustomError? onConflictError4 = error ("error msg 1", message = "error 1", code = 500); - CustomError? onConflictError5 = error ("error msg 2", message = "error 2", code = 500); + CustomErrorX? onConflictError4 = error("error msg 1", message = "error 1", code = 500); + CustomErrorX? onConflictError5 = error("error msg 2", message = "error 2", code = 500); - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 1, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 1, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList = [c1, c2, c3]; + CustomerX[] customerList = [c1, c2, c3]; var customerTable1 = table key(id) from var customer in customerList select { @@ -849,7 +862,10 @@ function testTableOnConflict() { } on conflict onConflictError2; - assertEqual(customerTable2, table key(id) [{"id":1,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}]); + assertEqual(customerTable2, table key(id) [ + {"id": 1, "name": "James", "noOfItems": 5}, + {"id": 3, "name": "Anne", "noOfItems": 20} + ]); var customerTable3 = table key(id) from var customer in customerList select { @@ -859,7 +875,10 @@ function testTableOnConflict() { } on conflict onConflictError3; - assertEqual(customerTable3, table key(id) [{"id":1,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}]); + assertEqual(customerTable3, table key(id) [ + {"id": 1, "name": "James", "noOfItems": 5}, + {"id": 3, "name": "Anne", "noOfItems": 20} + ]); var customerTable4 = table key(id) from var customer in customerList select { @@ -889,7 +908,10 @@ function testTableOnConflict() { } on conflict null; - assertEqual(customerTable6, table key(id) [{"id":1,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}]); + assertEqual(customerTable6, table key(id) [ + {"id": 1, "name": "James", "noOfItems": 5}, + {"id": 3, "name": "Anne", "noOfItems": 20} + ]); var customerTable7 = table key(id) from var customer in customerList select { @@ -899,7 +921,10 @@ function testTableOnConflict() { } on conflict (); - assertEqual(customerTable7, table key(id) [{"id":1,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}]); + assertEqual(customerTable7, table key(id) [ + {"id": 1, "name": "James", "noOfItems": 5}, + {"id": 3, "name": "Anne", "noOfItems": 20} + ]); } type Token record {| @@ -911,7 +936,8 @@ type TokenTable table key(idx); function testQueryConstructingTableWithOnConflictClauseHavingNonTableQueryInLetClause() { TokenTable|error tbl1 = table key(idx) from int i in 1 ... 3 - let int[] arr = from var j in 1 ... 3 select j + let int[] arr = from var j in 1 ... 3 + select j select { idx: arr[i - 1], value: "A" + i.toString() @@ -919,10 +945,10 @@ function testQueryConstructingTableWithOnConflictClauseHavingNonTableQueryInLetC on conflict error("Duplicate Key"); TokenTable expectedTbl = table [ - {"idx": 1, "value": "A1"}, - {"idx": 2, "value": "A2"}, - {"idx": 3, "value": "A3"} - ]; + {"idx": 1, "value": "A1"}, + {"idx": 2, "value": "A2"}, + {"idx": 3, "value": "A3"} + ]; assertEqual(true, tbl1 is TokenTable); if tbl1 is TokenTable { @@ -930,7 +956,8 @@ function testQueryConstructingTableWithOnConflictClauseHavingNonTableQueryInLetC } TokenTable|error tbl2 = table key(idx) from int i in [1, 2, 1] - let int[] arr = from var j in 1 ... 3 select j + let int[] arr = from var j in 1 ... 3 + select j select { idx: arr[i], value: "A" + i.toString() @@ -946,7 +973,8 @@ function testQueryConstructingTableWithOnConflictClauseHavingNonTableQueryInLetC function testQueryConstructingTableWithOnConflictClauseHavingNonTableQueryInWhereClause() { TokenTable|error tbl1 = table key(idx) from int i in 1 ... 3 let int[] arr = [1, 2, 3] - where arr == from int j in 1...3 select j + where arr == from int j in 1 ... 3 + select j select { idx: i, value: "A" + i.toString() @@ -954,10 +982,10 @@ function testQueryConstructingTableWithOnConflictClauseHavingNonTableQueryInWher on conflict error("Duplicate Key"); TokenTable expectedTbl = table [ - {"idx": 1, "value": "A1"}, - {"idx": 2, "value": "A2"}, - {"idx": 3, "value": "A3"} - ]; + {"idx": 1, "value": "A1"}, + {"idx": 2, "value": "A2"}, + {"idx": 3, "value": "A3"} + ]; assertEqual(true, tbl1 is TokenTable); if tbl1 is TokenTable { @@ -966,7 +994,8 @@ function testQueryConstructingTableWithOnConflictClauseHavingNonTableQueryInWher TokenTable|error tbl2 = table key(idx) from int i in [1, 2, 1] let int[] arr = [1, 2, 3] - where arr == from int j in 1...3 select j + where arr == from int j in 1 ... 3 + select j select { idx: i, value: "A" + i.toString() @@ -980,7 +1009,7 @@ function testQueryConstructingTableWithOnConflictClauseHavingNonTableQueryInWher } function testQueryConstructingTableWithOnConflictsWithVarRef() { - TokenTable|error tbl1 = table key(idx) from int i in [1, 2, 3, 1, 2, 3] + TokenTable|error tbl1 = table key(idx) from int i in [1, 2, 3, 1, 2, 3] let string value = "A" + i.toString() select { idx: i, @@ -1004,13 +1033,13 @@ function testQueryConstructingTableWithOnConflictsWithVarRef() { } function testMapConstructingQueryExpr() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] list1 = [c1, c2, c3]; + CustomerX[] list1 = [c1, c2, c3]; - map map1 = map from var customer in list1 + map map1 = map from var customer in list1 select [customer.id.toString(), customer]; assertEqual(map1, {"1": {id: 1, name: "Melina", noOfItems: 12}, "2": {id: 2, name: "James", noOfItems: 5}, "3": {id: 3, name: "Anne", noOfItems: 20}}); @@ -1045,37 +1074,37 @@ function testMapConstructingQueryExpr() { map map4 = map from var item in list4 select [item[0], item[1]]; - map expectedMap = {"a":123,"b":123,"c":error("Error"),"zero":0}; + map expectedMap = {"a": 123, "b": 123, "c": error("Error"), "zero": 0}; - assertEqual(expectedMap.length(), (> map4).length()); + assertEqual(expectedMap.length(), (>map4).length()); foreach var key in expectedMap.keys() { - assertEqual(expectedMap[key], (> map4)[key]); + assertEqual(expectedMap[key], (>map4)[key]); } } function testMapConstructingQueryExpr2() { map map1 = map from var e in map from var e in [1, 2, 10, 3, 5, 20] - order by e descending - select [e.toString(), e] - order by e ascending - select [e.toString(), e]; - assertEqual(map1, {"1":1,"2":2,"3":3,"5":5,"10":10,"20":20}); + order by e descending + select [e.toString(), e] + order by e ascending + select [e.toString(), e]; + assertEqual(map1, {"1": 1, "2": 2, "3": 3, "5": 5, "10": 10, "20": 20}); map map2 = map from var e in (from var e in [1, 2, 5, 4] - let int f = e / 2 - order by f ascending - select f) - order by e descending - select [e.toString(), e]; - assertEqual(map2, {"2":2,"1":1,"0":0}); + let int f = e / 2 + order by f ascending + select f) + order by e descending + select [e.toString(), e]; + assertEqual(map2, {"2": 2, "1": 1, "0": 0}); } function testMapConstructingQueryExprWithDuplicateKeys() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] list1 = [c1, c2, c3, c1, c2, c3]; + CustomerX[] list1 = [c1, c2, c3, c1, c2, c3]; var map1 = map from var customer in list1 select [customer.id.toString(), customer]; @@ -1104,12 +1133,12 @@ function testMapConstructingQueryExprWithDuplicateKeys() { } function testMapConstructingQueryExprWithOnConflict() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; ()|error conflictMsg1 = (); - Customer[] list1 = [c1, c2, c3, c1, c2, c3]; + CustomerX[] list1 = [c1, c2, c3, c1, c2, c3]; var mapWithOnConflict1 = map from var customer in list1 select [customer.id.toString(), customer] @@ -1136,7 +1165,7 @@ function testMapConstructingQueryExprWithOnConflict() { [string:Char, int:Signed16] t5 = ["b", 123]; [string, int] t6 = ["c", -123]; [string, int:Unsigned32] t7 = ["zero", 0]; - CustomError? onConflictError4 = error("error msg 1", message = "Error 2", code = 500); + CustomErrorX? onConflictError4 = error("error msg 1", message = "Error 2", code = 500); [string, int][] list3 = [t4, t5, t4, t6, t6, t7, t7, t4]; map|error map3 = map from var item in list3 @@ -1155,14 +1184,14 @@ function testMapConstructingQueryExprWithOnConflict() { function testMapConstructingQueryExprWithOtherClauses() { error onConflictError = error("Key Conflict", message = "cannot insert."); - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonX p1 = {firstName: "Amy", lastName: "Melina", age: 23}; + PersonX p2 = {firstName: "Frank", lastName: "James", age: 30}; - Customer[] customerList1 = [c1, c2, c2]; - Person[] personList = [p1, p2]; + CustomerX[] customerList1 = [c1, c2, c2]; + PersonX[] personList = [p1, p2]; var selectedCustomers1 = map from var customer in customerList1 from var person in personList @@ -1180,14 +1209,14 @@ function testMapConstructingQueryExprWithOtherClauses() { on conflict onConflictError; assertEqual(selectedCustomers1, {"Amy Melina": {"id": 1, "name": "Amy Melina"}}); - Customer c3 = {id: 1, name: "Melina", noOfItems: 22}; - Customer c4 = {id: 2, name: "James", noOfItems: 15}; - Customer c5 = {id: 1, name: "Melina", noOfItems: 10}; - Customer c6 = {id: 2, name: "James", noOfItems: 11}; + CustomerX c3 = {id: 1, name: "Melina", noOfItems: 22}; + CustomerX c4 = {id: 2, name: "James", noOfItems: 15}; + CustomerX c5 = {id: 1, name: "Melina", noOfItems: 10}; + CustomerX c6 = {id: 2, name: "James", noOfItems: 11}; - Customer[] customerList2 = [c1, c2, c3, c4, c5, c6]; + CustomerX[] customerList2 = [c1, c2, c3, c4, c5, c6]; - map|error selectedCustomers2 = map from var customer in customerList2 + map|error selectedCustomers2 = map from var customer in customerList2 from var person in personList let string fullName = person.firstName + " " + person.lastName where customer.name == person.lastName @@ -1224,195 +1253,218 @@ function testMapConstructingQueryExprWithOtherClauses() { function testMapConstructingQueryExprWithJoinClause() { error onConflictError = error("Key Conflict", message = "cannot insert."); - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; - Person p1 = {firstName: "Amy", lastName: "Melina", age: 23}; - Person p2 = {firstName: "Frank", lastName: "James", age: 30}; + PersonX p1 = {firstName: "Amy", lastName: "Melina", age: 23}; + PersonX p2 = {firstName: "Frank", lastName: "James", age: 30}; - Customer[] customerList1 = [c1, c2, c1]; - Person[] personList = [p1, p2]; + CustomerX[] customerList1 = [c1, c2, c1]; + PersonX[] personList = [p1, p2]; var customerMap1 = map from var customer in customerList1 join var person in personList on customer.name equals person.lastName - select [customer.id.toString(), { - id: customer.id, - name: person.firstName, - age: person.age, - noOfItems: customer.noOfItems - }] + select [ + customer.id.toString(), + { + id: customer.id, + name: person.firstName, + age: person.age, + noOfItems: customer.noOfItems + } + ] on conflict onConflictError; assertEqual(customerMap1, onConflictError); - Customer[] customerList2 = [c1, c2]; + CustomerX[] customerList2 = [c1, c2]; var customerMap2 = map from var customer in customerList2 join var person in personList on customer.name equals person.lastName - select [customer.id.toString(), { - id: customer.id, - name: person.firstName, - age: person.age, - noOfItems: customer.noOfItems - }] + select [ + customer.id.toString(), + { + id: customer.id, + name: person.firstName, + age: person.age, + noOfItems: customer.noOfItems + } + ] on conflict onConflictError; - assertEqual(customerMap2, {"1":{"id":1,"name":"Amy","age":23,"noOfItems":12},"2":{"id":2,"name":"Frank","age":30,"noOfItems":5}}); + assertEqual(customerMap2, {"1": {"id": 1, "name": "Amy", "age": 23, "noOfItems": 12}, "2": {"id": 2, "name": "Frank", "age": 30, "noOfItems": 5}}); } function testMapConstructingQueryExprWithLimitClause() { error onConflictError = error("Key Conflict", message = "cannot insert."); - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Melina", noOfItems: 25}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Melina", noOfItems: 25}; - Customer[] customerList = [c1, c2, c3]; + CustomerX[] customerList = [c1, c2, c3]; - map|error customerMap1 = map from var customer in customerList.toStream() + map|error customerMap1 = map from var customer in customerList.toStream() where customer.name == "Melina" limit 1 - select [customer.name, { - id: customer.id, - name: customer.name, - noOfItems: customer.noOfItems - }] + select [ + customer.name, + { + id: customer.id, + name: customer.name, + noOfItems: customer.noOfItems + } + ] on conflict onConflictError; - assertEqual(customerMap1, {"Melina":{"id":1,"name":"Melina","noOfItems":12}}); + assertEqual(customerMap1, {"Melina": {"id": 1, "name": "Melina", "noOfItems": 12}}); } function testMapConstructingQueryExprWithOrderByClause() { map sorted1 = map from var e in [1, 2, 10, 3, 5, 20] - order by e ascending - select [e.toString(), e]; - assertEqual(sorted1, {"1":1,"2":2,"3":3,"5":5,"10":10,"20":20}); + order by e ascending + select [e.toString(), e]; + assertEqual(sorted1, {"1": 1, "2": 2, "3": 3, "5": 5, "10": 10, "20": 20}); map sorted2 = map from var e in [1, 2, 10, 3, 5, 20] - order by e descending - select [e.toString(), e]; - assertEqual(sorted2, {"20":20,"10":10,"5":5,"3":3,"2":2,"1":1}); + order by e descending + select [e.toString(), e]; + assertEqual(sorted2, {"20": 20, "10": 10, "5": 5, "3": 3, "2": 2, "1": 1}); var sorted3 = map from var e in ["1", "2", "10", "3", "5", "20"] - order by e ascending - select [e, e] on conflict (); - assertEqual(sorted3, {"1":"1","10":"10","2":"2","20":"20","3":"3","5":"5"}); + order by e ascending + select [e, e] + on conflict (); + assertEqual(sorted3, {"1": "1", "10": "10", "2": "2", "20": "20", "3": "3", "5": "5"}); var sorted4 = map from var e in [1, 2, 5, 4] - let int f = e / 2 - order by f ascending - select [f.toString(), e] on conflict error("Error"); + let int f = e / 2 + order by f ascending + select [f.toString(), e] + on conflict error("Error"); assertEqual(sorted4, error("Error")); } type Error error; + type Json json; + type IntOrString int|string; + type ZeroOrOne 1|0; type MapOfJsonOrError map|Error; + type MapOfIntOrError map|Error; + type ErrorOrMapOfZeroOrOne Error|map; function testMapConstructingQueryExprWithReferenceTypes() { map sorted1 = map from var e in [1, 0, 1, 0, 1, 1] - order by e ascending - select [e.toString(), e]; - assertEqual(sorted1, {"0":0,"1":1}); + order by e ascending + select [e.toString(), e]; + assertEqual(sorted1, {"0": 0, "1": 1}); ErrorOrMapOfZeroOrOne sorted2 = map from var e in [1, 0, 1, 0, 0, 0] - order by e ascending - select [e.toString(), e]; - assertEqual(sorted2, {"0":0,"1":1}); + order by e ascending + select [e.toString(), e]; + assertEqual(sorted2, {"0": 0, "1": 1}); map sorted3 = map from var e in ["1", "2", "10", "3", "5", "20"] - order by e ascending - select [e, e] on conflict (); - assertEqual(sorted3, {"1":"1","10":"10","2":"2","20":"20","3":"3","5":"5"}); + order by e ascending + select [e, e] + on conflict (); + assertEqual(sorted3, {"1": "1", "10": "10", "2": "2", "20": "20", "3": "3", "5": "5"}); MapOfJsonOrError sorted4 = map from var e in ["1", "2", "10", "3", "5", "20"] - order by e ascending - select [e, e] on conflict error("Error"); - assertEqual(sorted4, {"1":"1","10":"10","2":"2","20":"20","3":"3","5":"5"}); + order by e ascending + select [e, e] + on conflict error("Error"); + assertEqual(sorted4, {"1": "1", "10": "10", "2": "2", "20": "20", "3": "3", "5": "5"}); MapOfIntOrError sorted5 = map from var e in [1, 2, 5, 4] - let int f = e / 2 - order by f ascending - select [f.toString(), e] on conflict error("Error"); + let int f = e / 2 + order by f ascending + select [f.toString(), e] + on conflict error("Error"); assertEqual(sorted5, error("Error")); } function testReadonlyTable() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList1 = [c1, c2, c3]; + CustomerX[] customerList1 = [c1, c2, c3]; - CustomerKeyLessTable & readonly customerTable1 = table key(id, name) from var customer in customerList1 + CustomerKeyLessTableX & readonly customerTable1 = table key(id, name) from var customer in customerList1 select { id: customer.id, name: customer.name, noOfItems: customer.noOfItems }; - any _ = customerTable1; - assertEqual((typeof(customerTable1)).toString(), "typedesc [{\"id\":1,\"name\":\"Melina\",\"noOfItems\":12},{\"id\":2,\"name\":\"James\",\"noOfItems\":5},{\"id\":3,\"name\":\"Anne\",\"noOfItems\":20}]"); - assertEqual(customerTable1.toString(), [{"id":1,"name":"Melina","noOfItems":12},{"id":2,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}].toString()); + any _ = customerTable1; + assertEqual((typeof (customerTable1)).toString(), "typedesc [{\"id\":1,\"name\":\"Melina\",\"noOfItems\":12},{\"id\":2,\"name\":\"James\",\"noOfItems\":5},{\"id\":3,\"name\":\"Anne\",\"noOfItems\":20}]"); + assertEqual(customerTable1.toString(), [{"id": 1, "name": "Melina", "noOfItems": 12}, {"id": 2, "name": "James", "noOfItems": 5}, {"id": 3, "name": "Anne", "noOfItems": 20}].toString()); - Customer[] & readonly customerList2 = [c1, c2, c3].cloneReadOnly(); + CustomerX[] & readonly customerList2 = [c1, c2, c3].cloneReadOnly(); - CustomerKeyLessTable & readonly|error customerTable2 = table key(id, name) from var customer in customerList2 + CustomerKeyLessTableX & readonly|error customerTable2 = table key(id, name) from var customer in customerList2 select { id: customer.id, name: customer.name, noOfItems: customer.noOfItems - } on conflict error("Error"); - any _ = (checkpanic customerTable2); - assertEqual((typeof(checkpanic customerTable2)).toString(), "typedesc [{\"id\":1,\"name\":\"Melina\",\"noOfItems\":12},{\"id\":2,\"name\":\"James\",\"noOfItems\":5},{\"id\":3,\"name\":\"Anne\",\"noOfItems\":20}]"); - assertEqual((checkpanic customerTable2).toString(), [{"id":1,"name":"Melina","noOfItems":12},{"id":2,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}].toString()); + } + on conflict error("Error"); + any _ = (checkpanic customerTable2); + assertEqual((typeof (checkpanic customerTable2)).toString(), "typedesc [{\"id\":1,\"name\":\"Melina\",\"noOfItems\":12},{\"id\":2,\"name\":\"James\",\"noOfItems\":5},{\"id\":3,\"name\":\"Anne\",\"noOfItems\":20}]"); + assertEqual((checkpanic customerTable2).toString(), [{"id": 1, "name": "Melina", "noOfItems": 12}, {"id": 2, "name": "James", "noOfItems": 5}, {"id": 3, "name": "Anne", "noOfItems": 20}].toString()); - CustomerKeyLessTable customerTable3 = table key(id, name) from var customer in customerList2 + CustomerKeyLessTableX customerTable3 = table key(id, name) from var customer in customerList2 select { id: customer.id, name: customer.name, noOfItems: customer.noOfItems - } on conflict (); - assertEqual((typeof(customerTable3)).toString(), "typedesc table key(id, name)"); - assertEqual(customerTable3.toString(), [{"id":1,"name":"Melina","noOfItems":12},{"id":2,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}].toString()); + } + on conflict (); + assertEqual((typeof (customerTable3)).toString(), "typedesc table key(id, name)"); + assertEqual(customerTable3.toString(), [{"id": 1, "name": "Melina", "noOfItems": 12}, {"id": 2, "name": "James", "noOfItems": 5}, {"id": 3, "name": "Anne", "noOfItems": 20}].toString()); } function testReadonlyTable2() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList1 = [c1, c2, c3]; + CustomerX[] customerList1 = [c1, c2, c3]; - CustomerTable & readonly customerTable1 = table key(id, name) from var customer in customerList1 + CustomerTableX & readonly customerTable1 = table key(id, name) from var customer in customerList1 select { id: customer.id, name: customer.name, noOfItems: customer.noOfItems }; - any _ = customerTable1; + any _ = customerTable1; assertEqual((typeof customerTable1).toString(), "typedesc [{\"id\":1,\"name\":\"Melina\",\"noOfItems\":12},{\"id\":2,\"name\":\"James\",\"noOfItems\":5},{\"id\":3,\"name\":\"Anne\",\"noOfItems\":20}]"); - assertEqual(customerTable1.toString(), [{"id":1,"name":"Melina","noOfItems":12},{"id":2,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}].toString()); + assertEqual(customerTable1.toString(), [{"id": 1, "name": "Melina", "noOfItems": 12}, {"id": 2, "name": "James", "noOfItems": 5}, {"id": 3, "name": "Anne", "noOfItems": 20}].toString()); - Customer[] customerList2 = [c1, c2, c3]; + CustomerX[] customerList2 = [c1, c2, c3]; - CustomerTable & readonly|error customerTable2 = table key(id, name) from var customer in customerList2 - select customer.cloneReadOnly() on conflict error("Error"); - any _ = (checkpanic customerTable2); - assertEqual((typeof(checkpanic customerTable2)).toString(), "typedesc [{\"id\":1,\"name\":\"Melina\",\"noOfItems\":12},{\"id\":2,\"name\":\"James\",\"noOfItems\":5},{\"id\":3,\"name\":\"Anne\",\"noOfItems\":20}]"); - assertEqual((checkpanic customerTable2).toString(), [{"id":1,"name":"Melina","noOfItems":12},{"id":2,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}].toString()); + CustomerTableX & readonly|error customerTable2 = table key(id, name) from var customer in customerList2 + select customer.cloneReadOnly() + on conflict error("Error"); + any _ = (checkpanic customerTable2); + assertEqual((typeof (checkpanic customerTable2)).toString(), "typedesc [{\"id\":1,\"name\":\"Melina\",\"noOfItems\":12},{\"id\":2,\"name\":\"James\",\"noOfItems\":5},{\"id\":3,\"name\":\"Anne\",\"noOfItems\":20}]"); + assertEqual((checkpanic customerTable2).toString(), [{"id": 1, "name": "Melina", "noOfItems": 12}, {"id": 2, "name": "James", "noOfItems": 5}, {"id": 3, "name": "Anne", "noOfItems": 20}].toString()); - CustomerTable customerTable3 = table key(id, name) from var customer in customerList2 + CustomerTableX customerTable3 = table key(id, name) from var customer in customerList2 select { id: customer.id, name: customer.name, noOfItems: customer.noOfItems - } on conflict (); - assertEqual((typeof(customerTable3)).toString(), "typedesc table key(id, name)"); - assertEqual(customerTable3.toString(), [{"id":1,"name":"Melina","noOfItems":12},{"id":2,"name":"James","noOfItems":5},{"id":3,"name":"Anne","noOfItems":20}].toString()); + } + on conflict (); + assertEqual((typeof (customerTable3)).toString(), "typedesc table key(id, name)"); + assertEqual(customerTable3.toString(), [{"id": 1, "name": "Melina", "noOfItems": 12}, {"id": 2, "name": "James", "noOfItems": 5}, {"id": 3, "name": "Anne", "noOfItems": 20}].toString()); } type IdRec record {| @@ -1420,76 +1472,89 @@ type IdRec record {| |}; function testReadonlyTable3() { - table key(id) & readonly|error tbl = table key(id) from var i in [1, 2, 3, 4, 2, 3] - select { - id: i - } on conflict (); - - assertEqual((typeof(tbl)).toString(), "typedesc [{\"id\":1},{\"id\":2},{\"id\":3},{\"id\":4}]"); - assertEqual(tbl, table key(id) [{"id":1},{"id":2},{"id":3},{"id":4}]); + table key(id) & readonly|error tbl = table key(id) from var i in [1, 2, 3, 4, 2, 3] + select { + id: i + } + on conflict (); - if tbl !is error { - IdRec? member1 = tbl[1]; - assertEqual(member1, {"id":1}); - } + assertEqual((typeof (tbl)).toString(), "typedesc [{\"id\":1},{\"id\":2},{\"id\":3},{\"id\":4}]"); + assertEqual(tbl, table key(id) [ + {"id": 1}, + {"id": 2}, + {"id": 3}, + {"id": 4} + ]); + + if tbl !is error { + IdRec? member1 = tbl[1]; + assertEqual(member1, {"id": 1}); + } - table & readonly tbl2 = table key() from var i in [1, 2, 3, 4, 2, 3] - select { - id: i - }; + table & readonly tbl2 = table key() from var i in [1, 2, 3, 4, 2, 3] + select { + id: i + }; - assertEqual((typeof(tbl2)).toString(), "typedesc [{\"id\":1},{\"id\":2},{\"id\":3},{\"id\":4},{\"id\":2},{\"id\":3}]"); - assertEqual(tbl2, table key() [{"id":1},{"id":2},{"id":3},{"id":4},{"id":2},{"id":3}]); + assertEqual((typeof (tbl2)).toString(), "typedesc [{\"id\":1},{\"id\":2},{\"id\":3},{\"id\":4},{\"id\":2},{\"id\":3}]"); + assertEqual(tbl2, table key() [ + {"id": 1}, + {"id": 2}, + {"id": 3}, + {"id": 4}, + {"id": 2}, + {"id": 3} + ]); } function testConstructingListOfTablesUsingQueryWithReadonly() { - table key(id) & readonly users1 = table [ + table key(id) & readonly users1 = table [ {id: 1, firstName: "John", lastName: "Doe", age: 25} ]; - (table key(id))[] uList = [users1]; + (table key(id))[] uList = [users1]; - (table key(id))[] & readonly result = from var user in uList - select user.cloneReadOnly(); - assertEqual((typeof(result)).toString(), "typedesc [[{\"id\":1,\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":25}]]"); - assertEqual(result, [table key(id) [{"id":1,"firstName":"John","lastName":"Doe","age":25}]]); + (table key(id))[] & readonly result = from var user in uList + select user.cloneReadOnly(); + assertEqual((typeof (result)).toString(), "typedesc [[{\"id\":1,\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":25}]]"); + assertEqual(result, [table key(id) [{"id": 1, "firstName": "John", "lastName": "Doe", "age": 25}]]); } function testConstructingListOfRecordsUsingQueryWithReadonly() { - Employee emp1 = {firstName: "A1", lastName: "B1", dept: "C1"}; - Employee emp2 = {firstName: "A2", lastName: "B2", dept: "C2"}; - Employee emp3 = {firstName: "A3", lastName: "B3", dept: "C3"}; + EmployeeX emp1 = {firstName: "A1", lastName: "B1", dept: "C1"}; + EmployeeX emp2 = {firstName: "A2", lastName: "B2", dept: "C2"}; + EmployeeX emp3 = {firstName: "A3", lastName: "B3", dept: "C3"}; - (Employee & readonly)[] & readonly result = from var user in [emp1, emp2, emp3] - select user.cloneReadOnly(); - assertEqual((typeof(result)).toString(), "typedesc [{\"firstName\":\"A1\",\"lastName\":\"B1\",\"dept\":\"C1\"},{\"firstName\":\"A2\",\"lastName\":\"B2\",\"dept\":\"C2\"},{\"firstName\":\"A3\",\"lastName\":\"B3\",\"dept\":\"C3\"}]"); - assertEqual(result, [{"firstName":"A1","lastName":"B1","dept":"C1"},{"firstName":"A2","lastName":"B2","dept":"C2"},{"firstName":"A3","lastName":"B3","dept":"C3"}]); + (EmployeeX & readonly)[] & readonly result = from var user in [emp1, emp2, emp3] + select user.cloneReadOnly(); + assertEqual((typeof (result)).toString(), "typedesc [{\"firstName\":\"A1\",\"lastName\":\"B1\",\"dept\":\"C1\"},{\"firstName\":\"A2\",\"lastName\":\"B2\",\"dept\":\"C2\"},{\"firstName\":\"A3\",\"lastName\":\"B3\",\"dept\":\"C3\"}]"); + assertEqual(result, [{"firstName": "A1", "lastName": "B1", "dept": "C1"}, {"firstName": "A2", "lastName": "B2", "dept": "C2"}, {"firstName": "A3", "lastName": "B3", "dept": "C3"}]); } function testConstructingListOfXMLsUsingQueryWithReadonly() { xml a = xml ` 1 John `; (xml & readonly)[] & readonly result = from var user in a - select user.cloneReadOnly(); - assertEqual((typeof(result)).toString(), "typedesc [` 1 `,` `,` John `]"); - assertEqual(result, [xml` 1 `,xml` `,xml` John `]); + select user.cloneReadOnly(); + assertEqual((typeof (result)).toString(), "typedesc [` 1 `,` `,` John `]"); + assertEqual(result, [xml ` 1 `, xml ` `, xml ` John `]); } type Type1 int[]|string; function testConstructingListOfListsUsingQueryWithReadonly() { Type1[] & readonly result = from var user in [[1, 2], "a", "b", [-1, int:MAX_VALUE]] - select user.cloneReadOnly(); - assertEqual((typeof(result)).toString(), "typedesc [[1,2],\"a\",\"b\",[-1,9223372036854775807]]"); - assertEqual(result, [[1,2],"a","b",[-1,9223372036854775807]]); + select user.cloneReadOnly(); + assertEqual((typeof (result)).toString(), "typedesc [[1,2],\"a\",\"b\",[-1,9223372036854775807]]"); + assertEqual(result, [[1, 2], "a", "b", [-1, 9223372036854775807]]); } function testConstructingListOfMapsUsingQueryWithReadonly() { map[] & readonly result = from var item in [[1, 2], "a", "b", [-1, int:MAX_VALUE]] - select {item: item}.cloneReadOnly(); + select {item: item}.cloneReadOnly(); - assertEqual((typeof(result)).toString(), "typedesc [{\"item\":[1,2]},{\"item\":\"a\"},{\"item\":\"b\"},{\"item\":[-1,9223372036854775807]}]"); - assertEqual(result, [{"item":[1,2]},{"item":"a"},{"item":"b"},{"item":[-1,9223372036854775807]}]); + assertEqual((typeof (result)).toString(), "typedesc [{\"item\":[1,2]},{\"item\":\"a\"},{\"item\":\"b\"},{\"item\":[-1,9223372036854775807]}]"); + assertEqual(result, [{"item": [1, 2]}, {"item": "a"}, {"item": "b"}, {"item": [-1, 9223372036854775807]}]); } type T record { @@ -1497,8 +1562,9 @@ type T record { }; function testConstructingListInRecordsUsingQueryWithReadonly() { - T rec1 = { params: from var s in ["a", "b", "c", "abc"] select s }; - assertEqual(rec1, {"params":["a","b","c","abc"]}); + T rec1 = {params: from var s in ["a", "b", "c", "abc"] + select s}; + assertEqual(rec1, {"params": ["a", "b", "c", "abc"]}); } type DepartmentDetails record { @@ -1513,69 +1579,77 @@ type ErrorOrImmutableMapOfInt ImmutableMapOfInt|error; function testReadonlyMap1() { map & readonly mp1 = map from var item in [["1", 1], ["2", 2], ["3", 3], ["4", 4]] - select item; - any _ = mp1; - assertEqual((typeof(mp1)).toString(), "typedesc {\"1\":1,\"2\":2,\"3\":3,\"4\":4}"); - assertEqual(mp1, {"1":1,"2":2,"3":3,"4":4}); + select item; + any _ = mp1; + assertEqual((typeof (mp1)).toString(), "typedesc {\"1\":1,\"2\":2,\"3\":3,\"4\":4}"); + assertEqual(mp1, {"1": 1, "2": 2, "3": 3, "4": 4}); ImmutableMapOfInt mp2 = map from var item in [["1", 1], ["2", 2], ["3", 3], ["4", 4]] - select item; - any _ = mp2; - assertEqual((typeof(mp2)).toString(), "typedesc {\"1\":1,\"2\":2,\"3\":3,\"4\":4}"); - assertEqual(mp2, {"1":1,"2":2,"3":3,"4":4}); - + select item; + any _ = mp2; + assertEqual((typeof (mp2)).toString(), "typedesc {\"1\":1,\"2\":2,\"3\":3,\"4\":4}"); + assertEqual(mp2, {"1": 1, "2": 2, "3": 3, "4": 4}); ImmutableMapOfDept mp3 = map from var item in ["ABC", "DEF", "XY"] - let DepartmentDetails & readonly dept = {dept: item} - select [item, dept]; - any _ = mp3; - assertEqual((typeof(mp3)).toString(), "typedesc {\"ABC\":{\"dept\":\"ABC\"},\"DEF\":{\"dept\":\"DEF\"},\"XY\":{\"dept\":\"XY\"}}"); - assertEqual(mp3, {"ABC":{"dept":"ABC"},"DEF":{"dept":"DEF"},"XY":{"dept":"XY"}}); + let DepartmentDetails & readonly dept = {dept: item} + select [item, dept]; + any _ = mp3; + assertEqual((typeof (mp3)).toString(), "typedesc {\"ABC\":{\"dept\":\"ABC\"},\"DEF\":{\"dept\":\"DEF\"},\"XY\":{\"dept\":\"XY\"}}"); + assertEqual(mp3, {"ABC": {"dept": "ABC"}, "DEF": {"dept": "DEF"}, "XY": {"dept": "XY"}}); ErrorOrImmutableMapOfInt mp4 = map from var item in [["1", 1], ["2", 2], ["3", 3], ["4", 4]] - where item[1] > 1 - select item; - any _ = (checkpanic mp4); - assertEqual((typeof(mp4)).toString(), "typedesc {\"2\":2,\"3\":3,\"4\":4}"); - assertEqual(mp4, {"2":2,"3":3,"4":4}); + where item[1] > 1 + select item; + any _ = (checkpanic mp4); + assertEqual((typeof (mp4)).toString(), "typedesc {\"2\":2,\"3\":3,\"4\":4}"); + assertEqual(mp4, {"2": 2, "3": 3, "4": 4}); [string:Char, int[]][] & readonly list = [["a", [1, 2]], ["b", [3, 4]], ["c", [4]], ["c", [3]]]; - map|error mp5 = map from var item in list select item; - assertEqual(mp5, {"a":[1,2],"b":[3,4],"c":[3]}); - - map & readonly|error mp6 = map from var item in list select item; - assertEqual(mp6, {"a":[1,2],"b":[3,4],"c":[3]}); - any _ = (checkpanic mp6); - - map & readonly|error mp7 = map from var item in list select item on conflict error("Error"); + map|error mp5 = map from var item in list + select item; + assertEqual(mp5, {"a": [1, 2], "b": [3, 4], "c": [3]}); + + map & readonly|error mp6 = map from var item in list + select item; + assertEqual(mp6, {"a": [1, 2], "b": [3, 4], "c": [3]}); + any _ = (checkpanic mp6); + + map & readonly|error mp7 = map from var item in list + select item + on conflict error("Error"); assertEqual(mp7, error("Error")); } function testReadonlyMap2() { map & readonly mp1 = map from var item in [["1", 1], ["2", 2], ["2", 3], ["4", 4]] - select [item[0], item[1] * 2] on conflict (); - assertEqual(mp1, {"1":2,"2":6,"4":8}); + select [item[0], item[1] * 2] + on conflict (); + assertEqual(mp1, {"1": 2, "2": 6, "4": 8}); ImmutableMapOfInt|error mp2 = map from var item in [["1", 1], ["2", 2], ["2", 3], ["4", 4]] - select item on conflict error("Error 1"); + select item + on conflict error("Error 1"); assertEqual(mp2, error("Error 1")); error? conflictMsg = error("Error 2"); ImmutableMapOfDept|error mp3 = map from var item in ["ABC", "DEF", "XY", "ABC"] - let DepartmentDetails & readonly dept = {dept: item} - select [item, dept] on conflict conflictMsg; + let DepartmentDetails & readonly dept = {dept: item} + select [item, dept] + on conflict conflictMsg; assertEqual(mp3, error("Error 2")); conflictMsg = null; ErrorOrImmutableMapOfInt mp4 = map from var item in [["1", 1], ["2", 2], ["3", 3], ["1", 4]] - where item[1] > 1 - select item on conflict conflictMsg; - assertEqual(mp4, {"2":2,"3":3,"1":4}); + where item[1] > 1 + select item + on conflict conflictMsg; + assertEqual(mp4, {"2": 2, "3": 3, "1": 4}); } class EvenNumberGenerator { int i = 0; - public isolated function next() returns record {| int value; |}|error { + + public isolated function next() returns record {|int value;|}|error { return error("Greater than 20!"); } } @@ -1590,19 +1664,19 @@ type NumberRecord record {| |}; function testQueryConstructingMapsAndTablesWithClausesMayCompleteSEarlyWithError() { - EvenNumberGenerator evenGen = new(); - stream evenNumberStream = new(evenGen); + EvenNumberGenerator evenGen = new (); + stream evenNumberStream = new (evenGen); map|error map1 = map from var item in evenNumberStream - select [item.toBalString(), item]; + select [item.toBalString(), item]; assertEqual(map1, error("Greater than 20!")); table|error table1 = table key() from var item in evenNumberStream - select {value: item}; + select {value: item}; assertEqual(table1, error("Greater than 20!")); table key(id)|error table2 = table key(id) from var item in evenNumberStream - select {id: item, value: item.toBalString()}; + select {id: item, value: item.toBalString()}; assertEqual(table2, error("Greater than 20!")); // Enable following tests after fixing issue - lang/#36746 @@ -1625,16 +1699,18 @@ function testQueryConstructingMapsAndTablesWithClausesMayCompleteSEarlyWithError // assertEqual(table4, error("Greater than 20!")); map|error map3 = map from var firstNo in [1, 4, 4, 10] - select [firstNo.toBalString(), firstNo] on conflict error("Error"); + select [firstNo.toBalString(), firstNo] + on conflict error("Error"); assertEqual(map3, error("Error")); table key(id)|error table6 = table key(id) from var firstNo in [1, 4, 4, 10] - select {id: firstNo, value: firstNo.toBalString()} on conflict error("Error"); + select {id: firstNo, value: firstNo.toBalString()} + on conflict error("Error"); assertEqual(table6, error("Error")); } function testQueryConstructingMapWithOnConflictsWithVarRef() { - map|error mp1 = map from int i in [1, 2, 3, 1, 2, 3] + map|error mp1 = map from int i in [1, 2, 3, 1, 2, 3] let string value = "A" + i.toString() select [i.toString(), value] on conflict error(string `Duplicate Key: ${i} Value: ${value}`); @@ -1652,98 +1728,124 @@ function testQueryConstructingMapWithOnConflictsWithVarRef() { } function testQueryConstructingMapsAndTablesWithClausesMayCompleteSEarlyWithError2() { - EvenNumberGenerator evenGen = new(); - stream evenNumberStream = new(evenGen); + EvenNumberGenerator evenGen = new (); + stream evenNumberStream = new (evenGen); - map|error map1 = map from var item in (stream from var integer in evenNumberStream select integer) - select [item.toBalString(), item]; + map|error map1 = map from var item in (stream from var integer in evenNumberStream + select integer) + select [item.toBalString(), item]; assertEqual(map1, error("Greater than 20!")); - table|error table1 = table key() from var item in (stream from var integer in evenNumberStream select integer) - select {value: item}; + table|error table1 = table key() from var item in (stream from var integer in evenNumberStream + select integer) + select {value: item}; assertEqual(table1, error("Greater than 20!")); - table key(id)|error table2 = table key(id) from var item in (stream from var integer in evenNumberStream select integer) - select {id: item, value: item.toBalString()}; + table key(id)|error table2 = table key(id) from var item in (stream from var integer in evenNumberStream + select integer) + select {id: item, value: item.toBalString()}; assertEqual(table2, error("Greater than 20!")); - map|error map3 = map from var item in (stream from var integer in (stream from var integer in evenNumberStream select integer) select integer) - select [item.toBalString(), item]; + map|error map3 = map from var item in (stream from var integer in (stream from var integer in evenNumberStream + select integer) + select integer) + select [item.toBalString(), item]; assertEqual(map3, error("Greater than 20!")); table|error table4 = table key() from var item in - (stream from var integer in (stream from var integer in evenNumberStream select integer) select integer) - select {value: item}; + (stream from var integer in (stream from var integer in evenNumberStream + select integer) + select integer) + select {value: item}; assertEqual(table4, error("Greater than 20!")); } type FooBar1 ("foo"|"bar"|string)[2]; + type FooBar2 ("foo"|"bar")[2]; + type FooBar3 "foo"|"bar"; + type FooBar4 "foo"|"bar"|string:Char; + type FooBar5 "foo"|"bar"|string; function testMapConstructingQueryExprWithStringSubtypes() { FooBar1[] list1 = [["key1", "foo"], ["key2", "foo"], ["key3", "foo"]]; - map|error mp1 = map from var item in list1 select item; - assertEqual(mp1, {"key1":"foo","key2":"foo","key3":"foo"}); + map|error mp1 = map from var item in list1 + select item; + assertEqual(mp1, {"key1": "foo", "key2": "foo", "key3": "foo"}); FooBar2[] list2 = [["foo", "foo"], ["bar", "foo"], ["foo", "foo"]]; - map|error mp2 = map from var item in list2 select item; - assertEqual(mp2, {"foo":"foo","bar":"foo"}); + map|error mp2 = map from var item in list2 + select item; + assertEqual(mp2, {"foo": "foo", "bar": "foo"}); FooBar3[][2] list3 = [["foo", "bar"], ["bar", "foo"], ["foo", "bar"]]; - map|error mp3 = map from var item in list3 select item; - assertEqual(mp3, {"foo":"bar","bar":"foo"}); + map|error mp3 = map from var item in list3 + select item; + assertEqual(mp3, {"foo": "bar", "bar": "foo"}); FooBar4[][2] list4 = [["foo", "4"], ["bar", "2"], ["foo", "3"]]; - map|error mp4 = map from var item in list4 select item; - assertEqual(mp4, {"foo":"3","bar":"2"}); - map|error mp5 = map from var item in list4 select item on conflict error("Error"); + map|error mp4 = map from var item in list4 + select item; + assertEqual(mp4, {"foo": "3", "bar": "2"}); + map|error mp5 = map from var item in list4 + select item + on conflict error("Error"); assertEqual(mp5, error("Error")); FooBar5[][2] list5 = [["key1", "1.4"], ["key2", "2"], ["key3", "3"]]; - map|error mp6 = map from var item in list5 select item; - assertEqual(mp6, {"key1":"1.4","key2":"2","key3":"3"}); + map|error mp6 = map from var item in list5 + select item; + assertEqual(mp6, {"key1": "1.4", "key2": "2", "key3": "3"}); [FooBar3, int|float][] list6 = [["foo", 1.4], ["bar", 2], ["foo", 3]]; - map|error mp7 = map from var item in list6 select item; - assertEqual(mp7, {"foo":3,"bar":2}); - map|error mp8 = map from var item in list6 select item on conflict error("Error"); + map|error mp7 = map from var item in list6 + select item; + assertEqual(mp7, {"foo": 3, "bar": 2}); + map|error mp8 = map from var item in list6 + select item + on conflict error("Error"); assertEqual(mp8, error("Error")); [FooBar4, int|float][] list7 = [["foo", 1.4], ["bar", 2], ["foo", 3]]; - map|error mp9 = map from var item in list7 select item; - assertEqual(mp9, {"foo":3,"bar":2}); - map|error mp10 = map from var item in list7 select item on conflict error("Error"); + map|error mp9 = map from var item in list7 + select item; + assertEqual(mp9, {"foo": 3, "bar": 2}); + map|error mp10 = map from var item in list7 + select item + on conflict error("Error"); assertEqual(mp10, error("Error")); [FooBar5, int|float][] list8 = [["key1", 1.4], ["key2", 2], ["key3", 3]]; - map|error mp11 = map from var item in list8 select item; - assertEqual(mp11, {"key1":1.4,"key2":2,"key3":3}); + map|error mp11 = map from var item in list8 + select item; + assertEqual(mp11, {"key1": 1.4, "key2": 2, "key3": 3}); } function testDiffQueryConstructsUsedAsFuncArgs() returns error? { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList = [c1, c2, c3]; + CustomerX[] customerList = [c1, c2, c3]; int tblLength = getTableLength(table key(id, name) from var customer in customerList - select { - id: customer.id, - name: customer.name, - noOfItems: customer.noOfItems - }); + select { + id: customer.id, + name: customer.name, + noOfItems: customer.noOfItems + }); assertEqual(tblLength, 3); FooBar1[] list1 = [["key1", "foo"], ["key2", "foo"], ["key3", "foo"]]; - int mapLength = getMapLength(map from var item in list1 select item); + int mapLength = getMapLength(map from var item in list1 + select item); assertEqual(mapLength, 3); } -function getTableLength(CustomerTable tbl) returns int { +function getTableLength(CustomerTableX tbl) returns int { return tbl.length(); } @@ -1837,9 +1939,9 @@ function testJoinedQueryExprConstructingMapWithRegExp() { let string:RegExp a = re `AB*(A|B|[ab-fgh]+(?im-x:[cdeg-k]??${v})|)|^|PQ?` select [re1.toString() + "1", re1.toString() + a.toString()]; assertEqual({ - A1: "AAB*(A|B|[ab-fgh]+(?im-x:[cdeg-k]??1)|)|^|PQ?", - B1: "BAB*(A|B|[ab-fgh]+(?im-x:[cdeg-k]??1)|)|^|PQ?" - }, arr3); + A1: "AAB*(A|B|[ab-fgh]+(?im-x:[cdeg-k]??1)|)|^|PQ?", + B1: "BAB*(A|B|[ab-fgh]+(?im-x:[cdeg-k]??1)|)|^|PQ?" + }, arr3); } type ModuleDecls [string, FuncDecl...]; @@ -1866,67 +1968,69 @@ function testInnerQueryConstructedWithCEP() { select [name, sig] ]; - assertEqual([["01",["func1",["foo",["int","string"],"boolean"]]],["02"]], decl); + assertEqual([["01", ["func1", ["foo", ["int", "string"], "boolean"]]], ["02"]], decl); } error onConflictError = error("Key Conflict", message = "cannot insert."); function testTableConstructQueryWithNonConflictingKeys() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList = [c1, c2, c3]; - CustomerTable|error customerTable = getQueryResult(onConflictError, customerList); - assertEqual(true, customerTable is CustomerTable); - CustomerTable expectedTableValue = table [{id: 1, name: "Melina", noOfItems: 12}, - {id: 2, name: "James", noOfItems: 5}, - {id: 3, name: "Anne", noOfItems: 20}]; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX[] customerList = [c1, c2, c3]; + CustomerTableX|error customerTable = getQueryResult(onConflictError, customerList); + assertEqual(true, customerTable is CustomerTableX); + CustomerTableX expectedTableValue = table [ + {id: 1, name: "Melina", noOfItems: 12}, + {id: 2, name: "James", noOfItems: 5}, + {id: 3, name: "Anne", noOfItems: 20} + ]; assertEqual(customerTable, expectedTableValue); } function testTableConstructQueryWithConflictingKeys() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList = [c1, c2, c3, c1]; - CustomerTable|error customerTable = getQueryResult(onConflictError, customerList); + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX[] customerList = [c1, c2, c3, c1]; + CustomerTableX|error customerTable = getQueryResult(onConflictError, customerList); assertEqual(customerTable is error, true); assertEqual((customerTable).message(), "Key Conflict"); } -function getQueryResult(error onConflictError, Customer[] customerList) returns CustomerTable|error { - return table key(id, name) from var customer in customerList - select { +function getQueryResult(error onConflictError, CustomerX[] customerList) returns CustomerTableX|error { + return table key(id, name) from var customer in customerList + select { id: customer.id, name: customer.name, noOfItems: customer.noOfItems - } - on conflict onConflictError; + } + on conflict onConflictError; } function testMapConstructNestedQueryWithConflictingKeys() { - Customer c1 = {id: 1, name: "Melina", noOfItems: 12}; - Customer c2 = {id: 2, name: "James", noOfItems: 5}; - Customer c3 = {id: 3, name: "Anne", noOfItems: 20}; - Customer[] customerList = [c1, c2, c3, c1]; + CustomerX c1 = {id: 1, name: "Melina", noOfItems: 12}; + CustomerX c2 = {id: 2, name: "James", noOfItems: 5}; + CustomerX c3 = {id: 3, name: "Anne", noOfItems: 20}; + CustomerX[] customerList = [c1, c2, c3, c1]; (anydata|error)[] result = from var i in [1] select table key(id, name) from var customer in customerList - select { - id: customer.id, - name: customer.name, - noOfItems: customer.noOfItems - } - on conflict error(string `Error key: ${customer.id} iteration: ${i}`); + select { + id: customer.id, + name: customer.name, + noOfItems: customer.noOfItems + } + on conflict error(string `Error key: ${customer.id} iteration: ${i}`); assertEqual(result[0] is error, true); assertEqual((result[0]).message(), "Error key: 1 iteration: 1"); } function testMapConstructQueryWithConflictingKeys() { - Customer c1 = {id: 1, name: "Abba", noOfItems: 10}; - Customer c2 = {id: 2, name: "Jim", noOfItems: 20}; - Customer c3 = {id: 3, name: "James", noOfItems: 30}; - Customer c4 = {id: 3, name: "Abba", noOfItems: 40}; - Customer[] customerList = [c1, c2, c3, c4]; + CustomerX c1 = {id: 1, name: "Abba", noOfItems: 10}; + CustomerX c2 = {id: 2, name: "Jim", noOfItems: 20}; + CustomerX c3 = {id: 3, name: "James", noOfItems: 30}; + CustomerX c4 = {id: 3, name: "Abba", noOfItems: 40}; + CustomerX[] customerList = [c1, c2, c3, c4]; anydata|error result = map from var {name, noOfItems} in customerList group by name select [name, [noOfItems]] diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/query/simple-query-with-defined-type.bal b/tests/jballerina-unit-test/src/test/resources/test-src/query/simple-query-with-defined-type.bal index 10b35e903ead..344c163bc83b 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/query/simple-query-with-defined-type.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/query/simple-query-with-defined-type.bal @@ -14,7 +14,7 @@ // specific language governing permissions and limitations // under the License. -type Person record {| +type PersonK record {| string firstName; string lastName; int age; @@ -33,35 +33,35 @@ type Employee record {| |}; type Person1 record {| - string firstName; - string lastName; - string deptAccess; - Address address; + string firstName; + string lastName; + string deptAccess; + Address address; |}; -type Address record{| +type Address record {| string city; string country; |}; -type Student record{| +type Student record {| string firstName; string lastName; float score; |}; -type Teacher1 record{ - //record type referencing - *Person1; - Student[] classStudents?; - //anonymous record type - record {| - int duration; - string qualitifications; - |} experience; +type Teacher1 record { + //record type referencing + *Person1; + Student[] classStudents?; + //anonymous record type + record {| + int duration; + string qualitifications; + |} experience; }; -type Subscription record{| +type Subscription record {| string firstName; string lastName; float score; @@ -70,6 +70,7 @@ type Subscription record{| class NumberGenerator { int i = 0; + public isolated function next() returns record {|int value;|}|error? { //closes the stream after 5 events if (self.i == 5) { @@ -82,6 +83,7 @@ class NumberGenerator { class NumberGeneratorWithError { int i = 0; + public isolated function next() returns record {|int value;|}|error? { if (self.i == 2) { return error("Custom error thrown explicitly."); @@ -93,6 +95,7 @@ class NumberGeneratorWithError { class NumberGeneratorWithError2 { int i = 0; + public isolated function next() returns record {|int value;|}|error { if (self.i == 2) { return error("Custom error thrown explicitly."); @@ -106,6 +109,7 @@ type ErrorR1 error>; class NumberGeneratorWithCustomError { int i = 0; + public isolated function next() returns record {|int value;|}|ErrorR1? { if (self.i == 3) { return error ErrorR1("Custom error", x = 1); @@ -116,10 +120,10 @@ class NumberGeneratorWithCustomError { } type ResultValue record {| - Person value; + PersonK value; |}; -function getRecordValue((record {| Person value; |}|error?)|(record {| Person value; |}?) returnedVal) returns Person? { +function getRecordValue((record {|PersonK value;|}|error?)|(record {|PersonK value;|}?) returnedVal) returns PersonK? { if (returnedVal is ResultValue) { return returnedVal.value; } else { @@ -127,97 +131,97 @@ function getRecordValue((record {| Person value; |}|error?)|(record {| Person va } } -function testSimpleSelectQueryWithSimpleVariable() returns Person[] { +function testSimpleSelectQueryWithSimpleVariable() returns PersonK[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonK[] personList = [p1, p2, p3]; - Person[] outputPersonList = + PersonK[] outputPersonList = from var person in personList - select { - firstName: person.firstName, - lastName: person.lastName, - age: person.age - }; + select { + firstName: person.firstName, + lastName: person.lastName, + age: person.age + }; return outputPersonList; } -function testSimpleSelectQueryWithRecordVariable() returns Person[] { +function testSimpleSelectQueryWithRecordVariable() returns PersonK[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonK[] personList = [p1, p2, p3]; - Person[] outputPersonList = - from var { firstName: nm1, lastName: nm2, age: a } in personList - select { - firstName: nm1, - lastName: nm2, - age: a - }; + PersonK[] outputPersonList = + from var {firstName: nm1, lastName: nm2, age: a} in personList + select { + firstName: nm1, + lastName: nm2, + age: a + }; return outputPersonList; } -function testSimpleSelectQueryWithRecordVariableV2() returns Person[] { +function testSimpleSelectQueryWithRecordVariableV2() returns PersonK[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonK[] personList = [p1, p2, p3]; - Person[] outputPersonList = - from var { firstName, lastName, age } in personList - select { - firstName: firstName, - lastName: lastName, - age: age - }; + PersonK[] outputPersonList = + from var {firstName, lastName, age} in personList + select { + firstName: firstName, + lastName: lastName, + age: age + }; return outputPersonList; } function testSimpleSelectQueryWithRecordVariableV3() returns Teacher[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonK[] personList = [p1, p2, p3]; Teacher[] outputPersonList = - from var { firstName, lastName, age } in personList - select { - firstName, - lastName - }; + from var {firstName, lastName, age} in personList + select { + firstName, + lastName + }; return outputPersonList; } -function testSimpleSelectQueryWithWhereClause() returns Person[] { +function testSimpleSelectQueryWithWhereClause() returns PersonK[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonK[] personList = [p1, p2, p3]; - Person[] outputPersonList = + PersonK[] outputPersonList = from var person in personList - where person.age >= 30 - select { - firstName: person.firstName, - lastName: person.lastName, - age: person.age - }; + where person.age >= 30 + select { + firstName: person.firstName, + lastName: person.lastName, + age: person.age + }; return outputPersonList; } @@ -227,8 +231,8 @@ function testQueryExpressionForPrimitiveType() returns boolean { int[] outputIntList = from var value in intList - where value > 20 - select value; + where value > 20 + select value; return outputIntList == [21, 25]; } @@ -239,123 +243,123 @@ function testQueryExpressionWithSelectExpression() returns boolean { string[] stringOutput = from var value in intList - select value.toString(); + select value.toString(); - return stringOutput == ["1","2", "3"]; + return stringOutput == ["1", "2", "3"]; } -function testFilteringNullElements() returns Person[] { +function testFilteringNullElements() returns PersonK[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person?[] personList = [p1, (), p2]; + PersonK?[] personList = [p1, (), p2]; - Person[] outputPersonList = - from var person in personList - where (person is Person) - select { - firstName: person.firstName, - lastName: person.lastName, - age: person.age - }; + PersonK[] outputPersonList = + from var person in personList + where (person is PersonK) + select { + firstName: person.firstName, + lastName: person.lastName, + age: person.age + }; return outputPersonList; } function testMapWithArity() returns boolean { map m = {a: "1A", b: "2B", c: "3C", d: "4D"}; string[] val = from var v in m - where v == "1A" - select v; + where v == "1A" + select v; return val == ["1A"]; } function testJSONArrayWithArity() returns boolean { json[] jdata = [{name: "bob", age: 10}, {name: "tom", age: 16}]; string[] val = from var v in jdata - select checkpanic v.name; + select checkpanic v.name; return val == ["bob", "tom"]; } function testArrayWithTuple() returns boolean { [int, string][] arr = [[1, "A"], [2, "B"], [3, "C"]]; string[] val = from var [i, v] in arr - where i == 3 - select v; + where i == 3 + select v; return val == ["C"]; } -function testFromClauseWithStream() returns Person[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 30}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 40}; - Person p3 = {firstName: "John", lastName: "David", age: 50}; +function testFromClauseWithStream() returns PersonK[] { + PersonK p1 = {firstName: "Alex", lastName: "George", age: 30}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 40}; + PersonK p3 = {firstName: "John", lastName: "David", age: 50}; - Person[] personList = [p1, p2, p3]; - stream streamedPersons = personList.toStream(); + PersonK[] personList = [p1, p2, p3]; + stream streamedPersons = personList.toStream(); - Person[] outputPersonList = + PersonK[] outputPersonList = from var person in streamedPersons - where person.age == 40 - select person; + where person.age == 40 + select person; return outputPersonList; } function testSimpleSelectQueryWithLetClause() returns Employee[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonK[] personList = [p1, p2, p3]; Employee[] outputPersonList = from var person in personList - let string depName = "HR", string companyName = "WSO2" - where person.age >= 30 - select { - firstName: person.firstName, - lastName: person.lastName, - department: depName, - company: companyName - }; + let string depName = "HR", string companyName = "WSO2" + where person.age >= 30 + select { + firstName: person.firstName, + lastName: person.lastName, + department: depName, + company: companyName + }; return outputPersonList; } -function testFunctionCallInVarDeclLetClause() returns Person[] { +function testFunctionCallInVarDeclLetClause() returns PersonK[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person[] personList = [p1, p2]; + PersonK[] personList = [p1, p2]; var outputPersonList = - from Person person in personList - let int twiceAge = mutiplyBy2(person.age) - select { - firstName: person.firstName, - lastName: person.lastName, - age: twiceAge - }; + from PersonK person in personList + let int twiceAge = mutiplyBy2(person.age) + select { + firstName: person.firstName, + lastName: person.lastName, + age: twiceAge + }; return outputPersonList; } -function testUseOfLetInWhereClause() returns Person[] { +function testUseOfLetInWhereClause() returns PersonK[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 18}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 22}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 18}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 22}; - Person[] personList = [p1, p2]; + PersonK[] personList = [p1, p2]; var outputPersonList = from var person in personList - let int twiceAge = mutiplyBy2(person.age) - where twiceAge > 40 - select { - firstName: person.firstName, - lastName: person.lastName, - age: twiceAge - }; + let int twiceAge = mutiplyBy2(person.age) + where twiceAge > 40 + select { + firstName: person.firstName, + lastName: person.lastName, + age: twiceAge + }; return outputPersonList; } @@ -369,8 +373,8 @@ public function testQueryWithStream() returns boolean { var numberStream = new stream(numGen); int[]|error oddNumberList = from int num in numberStream - where (num % 2 == 1) - select num; + where (num % 2 == 1) + select num; if (oddNumberList is error) { return false; } else { @@ -378,14 +382,13 @@ public function testQueryWithStream() returns boolean { } } - public function testQueryStreamWithError() { NumberGeneratorWithError numGen = new; var numberStream = new stream(numGen); int[]|error oddNumberList = from int num in numberStream - where (num % 2 == 1) - select num; + where (num % 2 == 1) + select num; if (oddNumberList is error) { return; } @@ -452,10 +455,10 @@ public function testQueryStreamWithDifferentCompletionTypes() { } } -function testOthersAssociatedWithRecordTypes() returns Teacher1[]{ +function testOthersAssociatedWithRecordTypes() returns Teacher1[] { - Person1 p1 = {firstName: "Alex", lastName: "George", deptAccess: "XYZ", address:{city:"NY", country:"America"}}; - Person1 p2 = {firstName: "Ranjan", lastName: "Fonseka", deptAccess: "XYZ", address:{city:"NY", country:"America"}}; + Person1 p1 = {firstName: "Alex", lastName: "George", deptAccess: "XYZ", address: {city: "NY", country: "America"}}; + Person1 p2 = {firstName: "Ranjan", lastName: "Fonseka", deptAccess: "XYZ", address: {city: "NY", country: "America"}}; Student s1 = {firstName: "Alex", lastName: "George", score: 82.5}; Student s2 = {firstName: "Ranjan", lastName: "Fonseka", score: 90.6}; @@ -464,142 +467,142 @@ function testOthersAssociatedWithRecordTypes() returns Teacher1[]{ Student[] studentList = [s1, s2]; Teacher1[] outputteacherList = - from var person in personList - let int period = 10, string degree = "B.Sc." - select{ - //change order of the record fields - firstName:person.firstName, - address: person.address, - //optional field - classStudents: studentList, - deptAccess: person.deptAccess, - //member access - lastName:person["lastName"], - //values for anonymous record fields - experience: { - duration: period, - qualitifications: degree - } - }; - - return outputteacherList; + from var person in personList + let int period = 10, string degree = "B.Sc." + select { + //change order of the record fields + firstName: person.firstName, + address: person.address, + //optional field + classStudents: studentList, + deptAccess: person.deptAccess, + //member access + lastName: person["lastName"], + //values for anonymous record fields + experience: { + duration: period, + qualitifications: degree + } + }; + + return outputteacherList; } function testQueryExprTupleTypedBinding2() returns boolean { - [int,int][] arr1 = [[1,2],[2,3],[3,4]]; - [int,int] arr2 = [1,2]; + [int, int][] arr1 = [[1, 2], [2, 3], [3, 4]]; + [int, int] arr2 = [1, 2]; int[] ouputList = - from [int,int] [a,b] in arr1 - let [int,int] [d1,d2] = arr2, int x=d1+d2 - where b > x - select a; + from [int, int] [a, b] in arr1 + let [int, int] [d1, d2] = arr2, int x = d1 + d2 + where b > x + select a; - return ouputList == [3]; + return ouputList == [3]; } -function testQueryExprWithTypeConversion() returns Person1[]{ +function testQueryExprWithTypeConversion() returns Person1[] { - Person1 p1 = {firstName: "Alex", lastName: "George", deptAccess: "XYZ", address:{city:"NY", country:"America"}}; - Person1 p2 = {firstName: "Ranjan", lastName: "Fonseka", deptAccess: "XYZ", address:{city:"NY", country:"America"}}; + Person1 p1 = {firstName: "Alex", lastName: "George", deptAccess: "XYZ", address: {city: "NY", country: "America"}}; + Person1 p2 = {firstName: "Ranjan", lastName: "Fonseka", deptAccess: "XYZ", address: {city: "NY", country: "America"}}; - map m = {city:"New York", country:"America"}; + map m = {city: "New York", country: "America"}; - Person1[] personList = [p1, p2]; + Person1[] personList = [p1, p2]; - Person1[] outputPersonList = - from var person in personList - select{ - firstName: person.firstName, - lastName: person.lastName, - deptAccess: person.deptAccess, - address: checkpanic m.cloneWithType(Address) - }; + Person1[] outputPersonList = + from var person in personList + select { + firstName: person.firstName, + lastName: person.lastName, + deptAccess: person.deptAccess, + address: checkpanic m.cloneWithType(Address) + }; - return outputPersonList; + return outputPersonList; } -function testQueryExprWithStreamMapAndFilter() returns Subscription[]{ +function testQueryExprWithStreamMapAndFilter() returns Subscription[] { Student s1 = {firstName: "Alex", lastName: "George", score: 82.5}; Student s2 = {firstName: "Ranjan", lastName: "Fonseka", score: 90.6}; Student[] studentList = [s1, s2]; - Subscription[] outputSubscriptionList = - from var subs in >studentList.toStream().filter(function (Student student) returns boolean { - return student.score > 85.3; - }).'map(function (Student student) returns Subscription { - Subscription subscription = { - firstName: student.firstName, - lastName: student.lastName, - score: student.score, - degree: "Bachelor of Medicine" - }; - return subscription; - }) - select subs; + Subscription[] outputSubscriptionList = + from var subs in >studentList.toStream().filter(function(Student student) returns boolean { + return student.score > 85.3; + }).'map(function(Student student) returns Subscription { + Subscription subscription = { + firstName: student.firstName, + lastName: student.lastName, + score: student.score, + degree: "Bachelor of Medicine" + }; + return subscription; + }) + select subs; - return outputSubscriptionList; + return outputSubscriptionList; } function testSimpleSelectQueryReturnStream() returns boolean { boolean testPassed = true; - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonK[] personList = [p1, p2, p3]; - stream outputPersonStream = stream from var person in personList - select { - firstName: person.firstName, - lastName: person.lastName, - age: person.age - }; - Person? returnedVal = getRecordValue(outputPersonStream.next()); - testPassed = testPassed && (returnedVal is Person) && (returnedVal == p1); + stream outputPersonStream = stream from var person in personList + select { + firstName: person.firstName, + lastName: person.lastName, + age: person.age + }; + PersonK? returnedVal = getRecordValue(outputPersonStream.next()); + testPassed = testPassed && (returnedVal is PersonK) && (returnedVal == p1); returnedVal = getRecordValue(outputPersonStream.next()); - testPassed = testPassed && (returnedVal is Person) && (returnedVal == p2); + testPassed = testPassed && (returnedVal is PersonK) && (returnedVal == p2); returnedVal = getRecordValue(outputPersonStream.next()); - testPassed = testPassed && (returnedVal is Person) && (returnedVal == p3); + testPassed = testPassed && (returnedVal is PersonK) && (returnedVal == p3); return testPassed; } function testQueryWithRecordVarInLetClause() returns Person1[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonK p3 = {firstName: "John", lastName: "David", age: 33}; Address address = {city: "Colombo", country: "SL"}; - Person[] personList = [p1, p2, p3]; + PersonK[] personList = [p1, p2, p3]; var outputPersonList = from var person in personList - let Address {city: town, country: state } = address - where person.age >= 30 - select { - firstName: person.firstName, - lastName: person.lastName, - deptAccess: "XYZ", - address: {city: town, country: state} - }; + let Address {city: town, country: state} = address + where person.age >= 30 + select { + firstName: person.firstName, + lastName: person.lastName, + deptAccess: "XYZ", + address: {city: town, country: state} + }; return outputPersonList; } function testForeachStream() returns boolean { - Person p1 = {firstName: "Alex", lastName: "George", age: 30}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 40}; - Person p3 = {firstName: "John", lastName: "David", age: 50}; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 30}; + PersonK p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 40}; + PersonK p3 = {firstName: "John", lastName: "David", age: 50}; - Person[] personList = [p1, p2, p3]; - stream streamedPersons = personList.toStream(); + PersonK[] personList = [p1, p2, p3]; + stream streamedPersons = personList.toStream(); - Person[] outputPersonList = []; - foreach Person person in streamedPersons { + PersonK[] outputPersonList = []; + foreach PersonK person in streamedPersons { if (person.age == 40) { outputPersonList.push(person); } @@ -614,8 +617,8 @@ function testForeachStream() returns boolean { function testTypeTestInWhereClause() { int?[] v = [1, 2, (), 3]; int[] result = from var i in v - where i is int - select i; + where i is int + select i; assertEquality(3, result.length()); assertEquality(1, result[0]); assertEquality(2, result[1]); @@ -718,16 +721,16 @@ function testJoinedQueryExprWithRegExp() { let string:RegExp a = re `AB*[^abc-efg](?:A|B|[ab-fgh]+(?im-x:[cdeg-k]??${v})|)|^|PQ?` select re1.toString() + a.toString(); assertEquality(true, [ - "AAB*[^abc-efg](?:A|B|[ab-fgh]+(?im-x:[cdeg-k]??1)|)|^|PQ?", - "BAB*[^abc-efg](?:A|B|[ab-fgh]+(?im-x:[cdeg-k]??1)|)|^|PQ?" - ] == arr3); + "AAB*[^abc-efg](?:A|B|[ab-fgh]+(?im-x:[cdeg-k]??1)|)|^|PQ?", + "BAB*[^abc-efg](?:A|B|[ab-fgh]+(?im-x:[cdeg-k]??1)|)|^|PQ?" + ] == arr3); } function testQueryExprWithLangLibCallsWithArrowFunctions() { - Person p1 = {firstName: "Alex", lastName: "George", age: 30}; - Person p2 = {firstName: "Anne", lastName: "Frank", age: 40}; - Person p3 = {firstName: "John", lastName: "David", age: 50}; - Person[] personList = [p1, p2, p3]; + PersonK p1 = {firstName: "Alex", lastName: "George", age: 30}; + PersonK p2 = {firstName: "Anne", lastName: "Frank", age: 40}; + PersonK p3 = {firstName: "John", lastName: "David", age: 50}; + PersonK[] personList = [p1, p2, p3]; int[] ageList = [50, 60]; int[] filteredAges = from int age in ageList @@ -744,7 +747,7 @@ function testQueryExprWithLangLibCallsWithArrowFunctions() { select ["John", "Frank"].filter(names => names == firstName).pop(); assertEquality(true, filteredNames2 == ["John"]); - Person[][] filteredPersons = from int age in [50] + PersonK[][] filteredPersons = from int age in [50] let string name = personList.filter(person => person.age == age).pop().firstName select personList.filter(person => person.firstName == name); assertEquality(true, filteredPersons == [[{"firstName":"John", "lastName":"David", "age":50}]]); diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/query/simple-query-with-var-type.bal b/tests/jballerina-unit-test/src/test/resources/test-src/query/simple-query-with-var-type.bal index 401b7a68af22..48af7090ea13 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/query/simple-query-with-var-type.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/query/simple-query-with-var-type.bal @@ -14,13 +14,13 @@ // specific language governing permissions and limitations // under the License. -type Person record {| +type PersonQT record {| string firstName; string lastName; int age; |}; -type Teacher record {| +type TeacherQT record {| string firstName; string lastName; int age; @@ -34,7 +34,7 @@ type EmployeeEntity record { int age; }; -type Employee record {| +type EmployeeQT record {| string fname; string lname; int age; @@ -42,6 +42,7 @@ type Employee record {| class NumberGenerator { int i = 0; + public isolated function next() returns record {|int value;|}|error? { //closes the stream after 5 events if (self.i == 5) { @@ -53,104 +54,104 @@ class NumberGenerator { } type ResultValue record {| - Person value; + PersonQT value; |}; -function getRecordValue((record {| Person value; |}|error?)|(record {| Person value; |}?) returnedVal) returns Person? { - if (returnedVal is ResultValue) { - return returnedVal.value; - } else { - return (); - } +function getRecordValue((record {|PersonQT value;|}|error?)|(record {|PersonQT value;|}?) returnedVal) returns PersonQT? { + if (returnedVal is ResultValue) { + return returnedVal.value; + } else { + return (); + } } -function testSimpleSelectQueryWithSimpleVariable() returns Person[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; +function testSimpleSelectQueryWithSimpleVariable() returns PersonQT[] { + PersonQT p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonQT p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonQT[] personList = [p1, p2, p3]; - Person[] outputPersonList = + PersonQT[] outputPersonList = from var person in personList - select { - firstName: person.firstName, - lastName: person.lastName, - age: person.age - }; + select { + firstName: person.firstName, + lastName: person.lastName, + age: person.age + }; return outputPersonList; } -function testSimpleSelectQueryWithRecordVariable() returns Person[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; +function testSimpleSelectQueryWithRecordVariable() returns PersonQT[] { + PersonQT p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonQT p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonQT[] personList = [p1, p2, p3]; - Person[] outputPersonList = - from var { firstName: nm1, lastName: nm2, age: a } in personList - select { - firstName: nm1, - lastName: nm2, - age: a - }; + PersonQT[] outputPersonList = + from var {firstName: nm1, lastName: nm2, age: a} in personList + select { + firstName: nm1, + lastName: nm2, + age: a + }; return outputPersonList; } -function testSimpleSelectQueryWithRecordVariableV2() returns Person[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; +function testSimpleSelectQueryWithRecordVariableV2() returns PersonQT[] { + PersonQT p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonQT p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonQT[] personList = [p1, p2, p3]; var outputPersonList = - from var { firstName, lastName, age } in personList - select { - firstName: firstName, - lastName: lastName, - age: age - }; + from var {firstName, lastName, age} in personList + select { + firstName: firstName, + lastName: lastName, + age: age + }; return outputPersonList; } -function testSimpleSelectQueryWithRecordVariableV3() returns Person[] { - Teacher p1 = {firstName: "Alex", lastName: "George", age: 23, teacherId: "XYZ01"}; - Teacher p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30, teacherId: "ABC01"}; - Teacher p3 = {firstName: "John", lastName: "David", age: 33, teacherId: "ABC10"}; +function testSimpleSelectQueryWithRecordVariableV3() returns PersonQT[] { + TeacherQT p1 = {firstName: "Alex", lastName: "George", age: 23, teacherId: "XYZ01"}; + TeacherQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30, teacherId: "ABC01"}; + TeacherQT p3 = {firstName: "John", lastName: "David", age: 33, teacherId: "ABC10"}; - Teacher[] teacherList = [p1, p2, p3]; + TeacherQT[] teacherList = [p1, p2, p3]; var outputPersonList = - from var { firstName, lastName, age, teacherId} in teacherList - select { - firstName: firstName, - lastName: lastName, - age: age - }; + from var {firstName, lastName, age, teacherId} in teacherList + select { + firstName: firstName, + lastName: lastName, + age: age + }; return outputPersonList; } -function testSimpleSelectQueryWithWhereClause() returns Person[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; +function testSimpleSelectQueryWithWhereClause() returns PersonQT[] { + PersonQT p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonQT p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonQT[] personList = [p1, p2, p3]; var outputPersonList = from var person in personList - where person.age >= 30 - select { - firstName: person.firstName, - lastName: person.lastName, - age: person.age - }; + where person.age >= 30 + select { + firstName: person.firstName, + lastName: person.lastName, + age: person.age + }; return outputPersonList; } @@ -160,8 +161,8 @@ function testQueryExpressionForPrimitiveType() returns boolean { var outputIntList = from var value in intList - where value > 20 - select value; + where value > 20 + select value; return outputIntList == [21, 25]; } @@ -172,102 +173,102 @@ function testQueryExpressionWithSelectExpression() returns boolean { var stringOutput = from var value in intList - select value.toString(); + select value.toString(); return stringOutput == ["1", "2", "3"]; } -function testFilteringNullElements() returns Person[] { +function testFilteringNullElements() returns PersonQT[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonQT p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person?[] personList = [p1, (), p2]; + PersonQT?[] personList = [p1, (), p2]; var outputPersonList = - from var person in personList - where (person is Person) - select { - firstName: person.firstName, - lastName: person.lastName, - age: person.age - }; + from var person in personList + where (person is PersonQT) + select { + firstName: person.firstName, + lastName: person.lastName, + age: person.age + }; return outputPersonList; } function testMapWithArity() returns boolean { map m = {a: "1A", b: "2B", c: "3C", d: "4D"}; var val = map from var v in m - where v == "1A" - select ["a", v]; + where v == "1A" + select ["a", v]; return val == {a: "1A"}; } function testJSONArrayWithArity() returns boolean { json[] jdata = [{name: "bob", age: 10}, {name: "tom", age: 16}]; var val = from var v in jdata - select checkpanic v.name; + select checkpanic v.name; return val == ["bob", "tom"]; } function testArrayWithTuple() returns boolean { [int, string][] arr = [[1, "A"], [2, "B"], [3, "C"]]; var val = from var [i, v] in arr - where i == 3 - select v; + where i == 3 + select v; return val == ["C"]; } -function testQueryExpressionWithVarType() returns Teacher[] { +function testQueryExpressionWithVarType() returns TeacherQT[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonQT p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonQT p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonQT[] personList = [p1, p2, p3]; var outputPersonList = from var person in personList - select { - firstName: person.firstName, - lastName: person.lastName, - age: person.age, - teacherId: "TER1200" - }; + select { + firstName: person.firstName, + lastName: person.lastName, + age: person.age, + teacherId: "TER1200" + }; return outputPersonList; } -function testSimpleSelectQueryWithSpreadOperator() returns Person[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; +function testSimpleSelectQueryWithSpreadOperator() returns PersonQT[] { + PersonQT p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonQT p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonQT[] personList = [p1, p2, p3]; - Person[] outputPersonList = + PersonQT[] outputPersonList = from var person in personList - select { - ...person - }; + select { + ...person + }; return outputPersonList; } -function testQueryExpressionWithSpreadOperatorV2() returns Teacher[] { +function testQueryExpressionWithSpreadOperatorV2() returns TeacherQT[] { - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonQT p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonQT p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonQT[] personList = [p1, p2, p3]; var outputPersonList = from var person in personList - select { - ...person, - teacherId: "TER1200" - }; + select { + ...person, + teacherId: "TER1200" + }; return outputPersonList; } @@ -277,11 +278,11 @@ public function testQueryWithStream() returns boolean { var numberStream = new stream(numGen); var oddNumberList = stream from var num in numberStream - where (num % 2 == 1) - select num; + where (num % 2 == 1) + select num; int[] result = []; - record {| int value; |}|error? v = oddNumberList.next(); - while (v is record {| int value; |}) { + record {|int value;|}|error? v = oddNumberList.next(); + while (v is record {|int value;|}) { result.push(v.value); v = oddNumberList.next(); } @@ -290,26 +291,26 @@ public function testQueryWithStream() returns boolean { function testSimpleSelectQueryReturnStream() returns boolean { boolean testPassed = true; - Person p1 = {firstName: "Alex", lastName: "George", age: 23}; - Person p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; - Person p3 = {firstName: "John", lastName: "David", age: 33}; + PersonQT p1 = {firstName: "Alex", lastName: "George", age: 23}; + PersonQT p2 = {firstName: "Ranjan", lastName: "Fonseka", age: 30}; + PersonQT p3 = {firstName: "John", lastName: "David", age: 33}; - Person[] personList = [p1, p2, p3]; + PersonQT[] personList = [p1, p2, p3]; var outputPersonStream = stream from var person in personList - select { - firstName: person.firstName, - lastName: person.lastName, - age: person.age - }; - Person? returnedVal = getRecordValue(outputPersonStream.next()); - testPassed = testPassed && (returnedVal is Person) && (returnedVal == p1); + select { + firstName: person.firstName, + lastName: person.lastName, + age: person.age + }; + PersonQT? returnedVal = getRecordValue(outputPersonStream.next()); + testPassed = testPassed && (returnedVal is PersonQT) && (returnedVal == p1); returnedVal = getRecordValue(outputPersonStream.next()); - testPassed = testPassed && (returnedVal is Person) && (returnedVal == p2); + testPassed = testPassed && (returnedVal is PersonQT) && (returnedVal == p2); returnedVal = getRecordValue(outputPersonStream.next()); - testPassed = testPassed && (returnedVal is Person) && (returnedVal == p3); + testPassed = testPassed && (returnedVal is PersonQT) && (returnedVal == p3); return testPassed; } @@ -318,14 +319,15 @@ string fname = ""; function testVariableShadowingWithQueryExpressions1() returns boolean { EmployeeEntity[] entities = [ - {id: 1232, fname: "Sameera", lname: "Jayasoma", age: 30}, - {id: 1232, fname: "Asanthi", lname: "Kulasinghe", age: 30}, - {id: 1232, fname: "Khiana", lname: "Jayasoma", age: 2} - ]; + {id: 1232, fname: "Sameera", lname: "Jayasoma", age: 30}, + {id: 1232, fname: "Asanthi", lname: "Kulasinghe", age: 30}, + {id: 1232, fname: "Khiana", lname: "Jayasoma", age: 2} + ]; - Employee[] records = from var {fname, lname, age} in entities select {fname, lname, age}; + EmployeeQT[] records = from var {fname, lname, age} in entities + select {fname, lname, age}; boolean testPassed = true; - Employee e = records[0]; + EmployeeQT e = records[0]; testPassed = testPassed && e.fname == "Sameera" && e.lname == "Jayasoma" && e.age == 30; e = records[1]; testPassed = testPassed && e.fname == "Asanthi" && e.lname == "Kulasinghe" && e.age == 30; @@ -337,15 +339,16 @@ function testVariableShadowingWithQueryExpressions1() returns boolean { function testVariableShadowingWithQueryExpressions2() returns boolean { EmployeeEntity[] entities = [ - {id: 1232, fname: "Sameera", lname: "Jayasoma", age: 30}, - {id: 1232, fname: "Asanthi", lname: "Kulasinghe", age: 30}, - {id: 1232, fname: "Khiana", lname: "Jayasoma", age: 2} - ]; + {id: 1232, fname: "Sameera", lname: "Jayasoma", age: 30}, + {id: 1232, fname: "Asanthi", lname: "Kulasinghe", age: 30}, + {id: 1232, fname: "Khiana", lname: "Jayasoma", age: 2} + ]; - Employee[] records = from var {fname, lname, age} in entities select {fname, lname, age}; + EmployeeQT[] records = from var {fname, lname, age} in entities + select {fname, lname, age}; var lname = 5; boolean testPassed = true; - Employee e = records[0]; + EmployeeQT e = records[0]; testPassed = testPassed && e.fname == "Sameera" && e.lname == "Jayasoma" && e.age == 30; e = records[1]; testPassed = testPassed && e.fname == "Asanthi" && e.lname == "Kulasinghe" && e.age == 30; @@ -371,7 +374,7 @@ function testSimpleSelectQueryWithTable() { assertEquality((table [{"id":1234},{"id":4567}]).toString(), t2.toString()); } -type User record { +type UserQuery record { readonly int id; string firstName; string lastName; @@ -379,10 +382,10 @@ type User record { }; function testQueryConstructingTableWithVar() returns error? { - User u1 = {id: 1, firstName: "John", lastName: "Doe", age: 25}; - User u2 = {id: 2, firstName: "Anne", lastName: "Frank", age: 30}; + UserQuery u1 = {id: 1, firstName: "John", lastName: "Doe", age: 25}; + UserQuery u2 = {id: 2, firstName: "Anne", lastName: "Frank", age: 30}; - table key(id) users = table []; + table key(id) users = table []; users.add(u1); users.add(u2); @@ -390,16 +393,16 @@ function testQueryConstructingTableWithVar() returns error? { where user.age > 21 && user.age < 60 select {user}; - assertEquality(true, result1 is table key(user)); + assertEquality(true, result1 is table key(user)); assertEquality({"user": u1}, result1.get(u1)); - User[] userList = [u1, u2]; + UserQuery[] userList = [u1, u2]; var result2 = check table key(user) from var user in userList where user.age > 21 && user.age < 60 select {user}; - assertEquality(true, result2 is table key(user)); + assertEquality(true, result2 is table key(user)); assertEquality({"user": u1}, result2.get(u1)); } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/record/readonly_record_fields.bal b/tests/jballerina-unit-test/src/test/resources/test-src/record/readonly_record_fields.bal index 2912906e6806..2d97b86a3e54 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/record/readonly_record_fields.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/record/readonly_record_fields.bal @@ -232,40 +232,40 @@ function testReadOnlyFieldWithDefaultValue() { assertEquality("cannot update 'readonly' field 'id' in record of type 'Identifier'", err.detail()["message"]); } -type Foo record {| +type FooH record {| string name; int id; float...; |}; -type Bar record {| +type BarH record {| readonly string name; readonly int id; |}; -type EmptyClosedRecord record {| +type EmptyClosedRecordH record {| |}; function testTypeReadOnlyFlagForAllReadOnlyFields() { - Bar st = { + BarH st = { name: "Maryam", id: 1234 }; - Foo & readonly pr = st; - assertTrue(pr is Bar); - assertTrue(pr is Bar & readonly); + FooH & readonly pr = st; + assertTrue(pr is BarH); + assertTrue(pr is BarH & readonly); assertEquality("Maryam", pr.name); assertEquality(1234, pr.id); readonly rd = st; - assertTrue(rd is Bar); - assertTrue(rd is Bar & readonly); + assertTrue(rd is BarH); + assertTrue(rd is BarH & readonly); - EmptyClosedRecord ecr = {}; + EmptyClosedRecordH ecr = {}; readonly rd2 = ecr; - assertTrue(rd2 is EmptyClosedRecord); - assertTrue(rd2 is EmptyClosedRecord & readonly); + assertTrue(rd2 is EmptyClosedRecordH); + assertTrue(rd2 is EmptyClosedRecordH & readonly); assertTrue(rd2 is record {} & readonly); } @@ -275,19 +275,19 @@ record {| function testTypeReadOnlyFlagForAllReadOnlyFieldsInAnonymousRecord() { readonly rd = modAnonRecord; - assertTrue( rd is record { int x; }); - assertTrue(rd is record { int x; } & readonly); - record { int x; } rec = checkpanic rd; + assertTrue(rd is record {int x;}); + assertTrue(rd is record {int x;} & readonly); + record {int x;} rec = checkpanic rd; assertEquality(2, rec.x); record {| readonly int x = 1; - readonly Bar y; + readonly BarH y; |} localAnonRecord = {y: {name: "Amy", id: 1001}}; readonly rd2 = localAnonRecord; - assertTrue( rd2 is record {| int x; Bar y; |}); - assertTrue(rd2 is record { int x; Bar y; } & readonly); - var rec2 = checkpanic rd2; + assertTrue(rd2 is record {|int x; BarH y;|}); + assertTrue(rd2 is record {int x; BarH y;} & readonly); + var rec2 = checkpanic rd2; assertEquality(1, rec2.x); assertEquality("Amy", rec2.y.name); assertEquality(1001, rec2.y.id); 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; diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/list-match-pattern-with-rest-match-pattern.bal b/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/list-match-pattern-with-rest-match-pattern.bal index 50ee838ae2d9..55982e369681 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/list-match-pattern-with-rest-match-pattern.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/list-match-pattern-with-rest-match-pattern.bal @@ -221,10 +221,11 @@ function testListMatchPatternWithRestPattern12() { assertEquals(false, a5[2]); } -class FooObj { +class FooObjLMP { public string s; public float f; public byte b; + function init(string s, float f, byte b) { self.s = s; self.f = f; @@ -232,9 +233,10 @@ class FooObj { } } -class BarObj { +class BarObjLMP { public boolean b; public int i; + function init(boolean b, int i) { self.b = b; self.i = i; @@ -242,36 +244,51 @@ class BarObj { } function testListMatchPatternWithRestPattern13() { - FooObj fooObj1 = new ("Fooo", 3.7, 23); - BarObj barObj1 = new (true, 56); - FooObj fooObj2 = new ("Foo2", 10.2, 30); - BarObj barObj2 = new (false, 56); - BarObj barObj3 = new (true, 58); + FooObjLMP fooObj1 = new ("Fooo", 3.7, 23); + BarObjLMP barObj1 = new (true, 56); + FooObjLMP fooObj2 = new ("Foo2", 10.2, 30); + BarObjLMP barObj2 = new (false, 56); + BarObjLMP barObj3 = new (true, 58); string matched = "Not Matched"; - [[string, [error, map, int, (FooObj|BarObj)...], Bar, (byte|float)...], string, boolean...] t2 = - [["Ballerina", [error("Error", detail1= 12, detail2= true), - {firstName: "John", lastName: "Damon"}, 12, fooObj1, barObj1], {id: 34, flag: true}, 10.5, 20], - "A", true, false]; + [[string, [error, map, int, (FooObjLMP|BarObjLMP)...], Bar, (byte|float)...], string, boolean...] t2 = + [ + [ + "Ballerina", + [ + error("Error", detail1 = 12, detail2 = true), + {firstName: "John", lastName: "Damon"}, + 12, + fooObj1, + barObj1 + ], + {id: 34, flag: true}, + 10.5, + 20 + ], + "A", + true, + false + ]; string a1; error a2; - [map, int, (FooObj|BarObj)...] a3; + [map, int, (FooObjLMP|BarObjLMP)...] a3; [Bar, (byte|float)...] a4; [string, boolean...] a5; map a6; - [int, (FooObj|BarObj)...] a7; + [int, (FooObjLMP|BarObjLMP)...] a7; string b1; error b2; - [map, int, (FooObj|BarObj)...] b3; + [map, int, (FooObjLMP|BarObjLMP)...] b3; [Bar, (byte|float)...] b4; [string, boolean...] b5; map b6; - [int, (FooObj|BarObj)...] b7; + [int, (FooObjLMP|BarObjLMP)...] b7; match t2 { - [[var g1, [var g2, ... var g3], ...var g4], ...var g5] => { + [[var g1, [var g2, ...var g3], ...var g4], ...var g5] => { matched = "Matched1"; a1 = g1; a2 = g2; @@ -286,9 +303,24 @@ function testListMatchPatternWithRestPattern13() { } } - [[g1, g2, ...g3], [...g5], ...g4] = [["Hello", error("Transaction Error"), [{primary: "Blue", - secondary: "Green"}, 1000, barObj2, fooObj2, barObj3]], [["World", true, false, true, false]], - [{id: 40, flag: true}, 0x5, 0x7, 20.25, 0x8]]; + [[g1, g2, ...g3], [...g5], ...g4] = [ + [ + "Hello", + error("Transaction Error"), + [ + { + primary: "Blue", + secondary: "Green" + }, + 1000, + barObj2, + fooObj2, + barObj3 + ] + ], + [["World", true, false, true, false]], + [{id: 40, flag: true}, 0x5, 0x7, 20.25, 0x8] + ]; b1 = g1; b2 = g2; b3 = g3; @@ -323,16 +355,16 @@ function testListMatchPatternWithRestPattern13() { assertEquals("Blue", b3[0]["primary"]); assertEquals("Green", b3[0]["secondary"]); assertEquals(1000, b3[1]); - assertEquals(true, b3[2] is BarObj); - assertEquals(false, (b3[2]).b); - assertEquals(56, (b3[2]).i); - assertEquals(true, b3[3] is FooObj); - assertEquals("Foo2", (b3[3]).s); - assertEquals(10.2, (b3[3]).f); - assertEquals(30, (b3[3]).b); - assertEquals(true, b3[4] is BarObj); - assertEquals(true, (b3[4]).b); - assertEquals(58, (b3[4]).i); + assertEquals(true, b3[2] is BarObjLMP); + assertEquals(false, (b3[2]).b); + assertEquals(56, (b3[2]).i); + assertEquals(true, b3[3] is FooObjLMP); + assertEquals("Foo2", (b3[3]).s); + assertEquals(10.2, (b3[3]).f); + assertEquals(30, (b3[3]).b); + assertEquals(true, b3[4] is BarObjLMP); + assertEquals(true, (b3[4]).b); + assertEquals(58, (b3[4]).i); assertEquals(5, b5.length()); assertEquals("World", b5[0]); assertEquals(true, b5[1]); diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/list-match-pattern.bal b/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/list-match-pattern.bal index 0395cd3756c2..32bb925e047d 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/list-match-pattern.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/list-match-pattern.bal @@ -504,35 +504,35 @@ function testListMatchPattern18() { assertEquals("Default" ,listMatchPattern18(a5)); } -type FooRec record { +type FooRecLMP record { string s; int i; float f; }; -type BarRec record { +type BarRecLMP record { byte b; - FooRec f; + FooRecLMP f; }; function listMatchPattern19(any a) returns string { match a { - [var i, var s] if i is FooRec && s is BarRec => { + [var i, var s] if i is FooRecLMP && s is BarRecLMP => { return "Matched with FooRec and BarRec : " + i.toString() + " , " + s.toString(); } - [var i, var s] if i is FooRec && s is float => { + [var i, var s] if i is FooRecLMP && s is float => { return "Matched with FooRec and float : " + i.toString() + " , " + s.toString(); } - [var i, var s] if i is BarRec && s is FooRec => { + [var i, var s] if i is BarRecLMP && s is FooRecLMP => { return "Matched with BarRec and FooRec : " + i.toString() + " , " + s.toString(); } - [var i, var s] if i is BarRec && s is int => { + [var i, var s] if i is BarRecLMP && s is int => { return "Matched with BarRec and int : " + i.toString() + " , " + s.toString(); } - [var i, var s] if i is float && s is FooRec => { + [var i, var s] if i is float && s is FooRecLMP => { return "Matched with float and FooRec : " + i.toString() + " , " + s.toString(); } - [var i, var s] if i is int && s is BarRec => { + [var i, var s] if i is int && s is BarRecLMP => { return "Matched with int and BarRec : " + i.toString() + " , " + s.toString(); } } @@ -541,34 +541,34 @@ function listMatchPattern19(any a) returns string { } function testListMatchPattern19() { - FooRec fooRec1 = {s: "S", i: 23, f: 5.6}; - BarRec barRec1 = {b: 12, f: fooRec1}; - - [int|FooRec, float|BarRec] | [float|BarRec, int|FooRec] a1 = [fooRec1, barRec1]; - [int|FooRec, float|BarRec] | [float|BarRec, int|FooRec] a2 = [fooRec1, 4.5]; - [int|FooRec, float|BarRec] | [float|BarRec, int|FooRec] a3 = [barRec1, fooRec1]; - [int|FooRec, float|BarRec] | [float|BarRec, int|FooRec] a4 = [barRec1, 543]; - [int|FooRec, float|BarRec] | [float|BarRec, int|FooRec] a5 = [5.2, fooRec1]; - [int|FooRec, float|BarRec] | [float|BarRec, int|FooRec] a6 = [15, barRec1]; - [int|FooRec, float|BarRec] | [float|BarRec, int|FooRec] a7 = [65, 7.4]; - [int|FooRec, float|BarRec] | [float|BarRec, int|FooRec] a8 = [3.6, 42]; + FooRecLMP fooRec1 = {s: "S", i: 23, f: 5.6}; + BarRecLMP barRec1 = {b: 12, f: fooRec1}; + + [int|FooRecLMP, float|BarRecLMP]|[float|BarRecLMP, int|FooRecLMP] a1 = [fooRec1, barRec1]; + [int|FooRecLMP, float|BarRecLMP]|[float|BarRecLMP, int|FooRecLMP] a2 = [fooRec1, 4.5]; + [int|FooRecLMP, float|BarRecLMP]|[float|BarRecLMP, int|FooRecLMP] a3 = [barRec1, fooRec1]; + [int|FooRecLMP, float|BarRecLMP]|[float|BarRecLMP, int|FooRecLMP] a4 = [barRec1, 543]; + [int|FooRecLMP, float|BarRecLMP]|[float|BarRecLMP, int|FooRecLMP] a5 = [5.2, fooRec1]; + [int|FooRecLMP, float|BarRecLMP]|[float|BarRecLMP, int|FooRecLMP] a6 = [15, barRec1]; + [int|FooRecLMP, float|BarRecLMP]|[float|BarRecLMP, int|FooRecLMP] a7 = [65, 7.4]; + [int|FooRecLMP, float|BarRecLMP]|[float|BarRecLMP, int|FooRecLMP] a8 = [3.6, 42]; assertEquals("Matched with FooRec and BarRec : {\"s\":\"S\",\"i\":23,\"f\":5.6} , " + "{\"b\":12,\"f\":{\"s\":\"S\",\"i\":23,\"f\":5.6}}", listMatchPattern19(a1)); - assertEquals("Matched with FooRec and float : {\"s\":\"S\",\"i\":23,\"f\":5.6} , 4.5" ,listMatchPattern19(a2)); + assertEquals("Matched with FooRec and float : {\"s\":\"S\",\"i\":23,\"f\":5.6} , 4.5", listMatchPattern19(a2)); assertEquals("Matched with BarRec and FooRec : {\"b\":12,\"f\":{\"s\":\"S\",\"i\":23,\"f\":5.6}} , " + - "{\"s\":\"S\",\"i\":23,\"f\":5.6}" ,listMatchPattern19(a3)); - assertEquals("Matched with BarRec and int : {\"b\":12,\"f\":{\"s\":\"S\",\"i\":23,\"f\":5.6}} , 543" , - listMatchPattern19(a4)); - assertEquals("Matched with float and FooRec : 5.2 , {\"s\":\"S\",\"i\":23,\"f\":5.6}" ,listMatchPattern19(a5)); - assertEquals("Matched with int and BarRec : 15 , {\"b\":12,\"f\":{\"s\":\"S\",\"i\":23,\"f\":5.6}}" , - listMatchPattern19(a6)); - assertEquals("Default" ,listMatchPattern19(a7)); - assertEquals("Default" ,listMatchPattern19(a8)); + "{\"s\":\"S\",\"i\":23,\"f\":5.6}", listMatchPattern19(a3)); + assertEquals("Matched with BarRec and int : {\"b\":12,\"f\":{\"s\":\"S\",\"i\":23,\"f\":5.6}} , 543", + listMatchPattern19(a4)); + assertEquals("Matched with float and FooRec : 5.2 , {\"s\":\"S\",\"i\":23,\"f\":5.6}", listMatchPattern19(a5)); + assertEquals("Matched with int and BarRec : 15 , {\"b\":12,\"f\":{\"s\":\"S\",\"i\":23,\"f\":5.6}}", + listMatchPattern19(a6)); + assertEquals("Default", listMatchPattern19(a7)); + assertEquals("Default", listMatchPattern19(a8)); } function listMatchPattern20() returns string { - [boolean, string] | [int, string, decimal] v = [1, "A", 1.1d]; + [boolean, string]|[int, string, decimal] v = [1, "A", 1.1d]; match v { [var i, ...var s] => { return "i: " + i.toString() + " s: " + s.toString(); @@ -699,7 +699,7 @@ function testListMatchPatternWithWildCard() { result = "Matched"; } _ => { - result = "Default"; + result = "Default"; } } assertEquals("Default", result); @@ -711,7 +711,7 @@ function testListMatchPatternWithWildCard() { result = "Matched"; } _ => { - result = "Default"; + result = "Default"; } } assertEquals("Not Matched", result); @@ -719,10 +719,10 @@ function testListMatchPatternWithWildCard() { function testListMatchPatternWithArrayAndAnydataIntersection() { int[] x = [1, 2, 3]; - assertEquals(x, listMatchPattern28( [x])); + assertEquals(x, listMatchPattern28([x])); anydata[] y = [["hello", "world"]]; assertEquals(["hello", "world"], listMatchPattern28(y)); - assertEquals("other", listMatchPattern28( [["hello", "world"], 1, 2])); + assertEquals("other", listMatchPattern28([["hello", "world"], 1, 2])); assertEquals("other", listMatchPattern28("hello")); } @@ -745,12 +745,12 @@ function testListMatchPattern29() { assertEquals((), listMatchPattern29(1)); } -type Rec record {| +type RecLMP record {| int|float a; |}; function testListMatchPattern30() { - [int, Rec|string] a1 = [12, {a: 1}]; + [int, RecLMP|string] a1 = [12, {a: 1}]; string result = ""; match a1 { @@ -781,7 +781,7 @@ function testListMatchPattern30() { } assertEquals("Pattern3", result); - [int, Rec|string...] a2 = [12, {a: 1}]; + [int, RecLMP|string...] a2 = [12, {a: 1}]; result = ""; match a2 { @@ -797,7 +797,7 @@ function testListMatchPattern30() { } assertEquals("Pattern3", result); - [int, string, Rec|string...] a3 = [12, "C", {a: 1.5}]; + [int, string, RecLMP|string...] a3 = [12, "C", {a: 1.5}]; result = ""; match a3 { @@ -813,7 +813,7 @@ function testListMatchPattern30() { } assertEquals("Pattern2", result); - [Rec|string...] a4 = [{a: 1}, {a: 2}, {a: 3}]; + [RecLMP|string...] a4 = [{a: 1}, {a: 2}, {a: 3}]; result = ""; match a4 { @@ -838,8 +838,8 @@ function testListMatchPattern30() { } assertEquals("Pattern2", result); - error err1 = error("Error One", data= [{b: 5}, 12]); - [error, Rec|string...] a5 = [err1, {a: 2}, {a: 3}]; + error err1 = error("Error One", data = [{b: 5}, 12]); + [error, RecLMP|string...] a5 = [err1, {a: 2}, {a: 3}]; result = ""; match a5 { @@ -853,21 +853,22 @@ function testListMatchPattern30() { assertEquals("Pattern2", result); } -type T readonly & S; -type S [INT, int]|[STRING, string]; +type TLMP readonly & SLMP; + +type SLMP [INT, int]|[STRING, string]; const INT = 1; const STRING = 2; function testListMatchPattern31() { - T t1 = [STRING, "hello"]; - T t2 = [INT, 1234]; + TLMP t1 = [STRING, "hello"]; + TLMP t2 = [INT, 1234]; assertEquals(["hello", ()], listMatchPattern31(t1)); assertEquals([(), 1234], listMatchPattern31(t2)); } -function listMatchPattern31(T t) returns [string?, int?] { +function listMatchPattern31(TLMP t) returns [string?, int?] { string? s = (); int? i = (); @@ -884,14 +885,14 @@ function listMatchPattern31(T t) returns [string?, int?] { } function testListMatchPattern32() { - T t1 = [STRING, "hello"]; - T t2 = [INT, 1234]; + TLMP t1 = [STRING, "hello"]; + TLMP t2 = [INT, 1234]; assertEquals("hello", listMatchPattern32(t1)); assertEquals(1234, listMatchPattern32(t2)); } -function listMatchPattern32(T t) returns string|int { +function listMatchPattern32(TLMP t) returns string|int { string|int s; match t { @@ -903,19 +904,19 @@ function listMatchPattern32(T t) returns string|int { return s; } -type T2 readonly & ([1, string]|[2, string]|[3, string]); +type T2LMP readonly & ([1, string]|[2, string]|[3, string]); function testListMatchPattern33() { - T2 t1 = [1, "hello"]; - T2 t2 = [2, "1234"]; - T2 t3 = [3, "abcd"]; + T2LMP t1 = [1, "hello"]; + T2LMP t2 = [2, "1234"]; + T2LMP t3 = [3, "abcd"]; assertEquals("hello", listMatchPattern33(t1)); assertEquals("1234", listMatchPattern33(t2)); assertEquals("abcd", listMatchPattern33(t3)); } -function listMatchPattern33(T2 t) returns string { +function listMatchPattern33(T2LMP t) returns string { string s; match t { @@ -927,18 +928,19 @@ function listMatchPattern33(T2 t) returns string { return s; } -type T3 readonly & S3; -type S3 string[2]|int[2]; +type T3LMP readonly & S3LMP; + +type S3LMP string[2]|int[2]; function testListMatchPattern34() { - T3 t1 = ["1", "hello"]; - T3 t2 = [2, 1234]; + T3LMP t1 = ["1", "hello"]; + T3LMP t2 = [2, 1234]; assertEquals("hello", listMatchPattern34(t1)); assertEquals(1234, listMatchPattern34(t2)); } -function listMatchPattern34(T3 t) returns string|int { +function listMatchPattern34(T3LMP t) returns string|int { string|int s = 10; match t { @@ -950,22 +952,22 @@ function listMatchPattern34(T3 t) returns string|int { return s; } -public type T4 ["list", T4[]]|"int"; +public type T4LMP ["list", T4LMP[]]|"int"; function testListMatchPattern35() { - T4[] t1 = ["int"]; - T4[] t2 = ["int", "int", "int"]; + T4LMP[] t1 = ["int"]; + T4LMP[] t2 = ["int", "int", "int"]; - T4 x1 = ["list", t1]; - T4 x2 = ["list", ["int", "int"]]; - T4 x3 = ["list", t2]; + T4LMP x1 = ["list", t1]; + T4LMP x2 = ["list", ["int", "int"]]; + T4LMP x3 = ["list", t2]; assertEquals(listMatchPattern35(x1, t1), "match 4"); assertEquals(listMatchPattern35("int", ()), "match 1"); assertEquals(listMatchPattern35(x2, ()), "match 2"); assertEquals(listMatchPattern35(x3, t2), "match 4"); } -function listMatchPattern35(T4 x, T4[]? t) returns string? { +function listMatchPattern35(T4LMP x, T4LMP[]? t) returns string? { match x { "int" => { return "match 1"; @@ -987,19 +989,19 @@ function listMatchPattern35(T4 x, T4[]? t) returns string? { } function testListMatchPattern36() { - T4[] t1 = ["int"]; - T4[] t2 = ["int", "int", "int"]; + T4LMP[] t1 = ["int"]; + T4LMP[] t2 = ["int", "int", "int"]; - T4 x1 = ["list", t1]; - T4 x2 = ["list", ["int", "int"]]; - T4 x3 = ["list", t2]; + T4LMP x1 = ["list", t1]; + T4LMP x2 = ["list", ["int", "int"]]; + T4LMP x3 = ["list", t2]; assertEquals(listMatchPattern36(x1, t1), "match 4"); assertEquals(listMatchPattern36("int", ()), "match 1"); assertEquals(listMatchPattern36(x2, ()), "match 2"); assertEquals(listMatchPattern36(x3, t2), "match 4"); } -function listMatchPattern36((T4|anydata)? x, T4[]? t) returns string? { +function listMatchPattern36((T4LMP|anydata)? x, T4LMP[]? t) returns string? { string? a = (); match x { "int" => { @@ -1018,16 +1020,17 @@ function listMatchPattern36((T4|anydata)? x, T4[]? t) returns string? { } } -public type T5 ["array", T6]|["cell", T6, string]; -public type T6 ["|", T6]|"int"; +public type T5LMP ["array", T6LMP]|["cell", T6LMP, string]; + +public type T6LMP ["|", T6LMP]|"int"; function testListMatchPattern37() { - T6 t1 = ["|", ["|", "int"]]; - T6 t2 = "int"; - T5 x1 = ["cell", t1, "inf"]; - T5 x2 = ["array", t1]; - T5 x3 = ["cell", t2, "inf1"]; - T5 x4 = ["array", t2]; + T6LMP t1 = ["|", ["|", "int"]]; + T6LMP t2 = "int"; + T5LMP x1 = ["cell", t1, "inf"]; + T5LMP x2 = ["array", t1]; + T5LMP x3 = ["cell", t2, "inf1"]; + T5LMP x4 = ["array", t2]; assertEquals(listMatchPattern37(x1, t1, "inf"), "match 2"); assertEquals(listMatchPattern37(x2, t1, ()), "match 4"); @@ -1035,7 +1038,7 @@ function testListMatchPattern37() { assertEquals(listMatchPattern37(x4, t2, ()), "match 4"); } -function listMatchPattern37(T5 x, T6? t, string? s) returns string { +function listMatchPattern37(T5LMP x, T6LMP? t, string? s) returns string { match x { ["cell", "int", "inf1"] => { return "match 1"; @@ -1059,12 +1062,12 @@ function listMatchPattern37(T5 x, T6? t, string? s) returns string { } function testListMatchPattern38() { - T6 t1 = ["|", ["|", "int"]]; - T6 t2 = "int"; - T5 x1 = ["cell", t1, "inf"]; - T5 x2 = ["array", t1]; - T5 x3 = ["cell", t2, "inf1"]; - T5 x4 = ["array", t2]; + T6LMP t1 = ["|", ["|", "int"]]; + T6LMP t2 = "int"; + T5LMP x1 = ["cell", t1, "inf"]; + T5LMP x2 = ["array", t1]; + T5LMP x3 = ["cell", t2, "inf1"]; + T5LMP x4 = ["array", t2]; assertEquals(listMatchPattern38(x1, t1, "inf"), "match 2"); assertEquals(listMatchPattern38(x2, t1, ()), "match 4"); @@ -1072,7 +1075,7 @@ function testListMatchPattern38() { assertEquals(listMatchPattern38(x4, t2, ()), "match 4"); } -function listMatchPattern38((anydata|T5)? x, T6? t, string? s) returns string? { +function listMatchPattern38((anydata|T5LMP)? x, T6LMP? t, string? s) returns string? { match x { ["cell", "int", "inf1"] => { return "match 1"; @@ -1092,40 +1095,40 @@ function listMatchPattern38((anydata|T5)? x, T6? t, string? s) returns string? { } } -public type T7 ["array", T6]|["cell", T6]; +public type T7LMP ["array", T6LMP]|["cell", T6LMP]; function testListMatchPattern39() { - T6 y1 = "int"; - T6 y2 = ["|", "int"]; + T6LMP y1 = "int"; + T6LMP y2 = ["|", "int"]; - T7 x1 = ["cell", y1]; - T7 x2 = ["array", y1]; - T7 x3 = ["cell", y2]; + T7LMP x1 = ["cell", y1]; + T7LMP x2 = ["array", y1]; + T7LMP x3 = ["cell", y2]; assertEquals(listMatchPattern39(x1, y1), "match 3"); assertEquals(listMatchPattern39(x2, y1), "match 2"); assertEquals(listMatchPattern39(x3, y2), "match 3"); } -function listMatchPattern39(T7 x, T6 y) returns string { +function listMatchPattern39(T7LMP x, T6LMP y) returns string { match x { ["list", var _] => { - T6 _ = x[1]; - T6 a = x[1]; + T6LMP _ = x[1]; + T6LMP a = x[1]; assertEquals(a, y); assertEquals(x[0], "list"); return "match 1"; } ["array", var _] => { - T6 _ = x[1]; - T6 a = x[1]; + T6LMP _ = x[1]; + T6LMP a = x[1]; assertEquals(a, y); assertEquals(x[0], "array"); return "match 2"; } ["cell", var _] => { - T6 _ = x[1]; - T6 a = x[1]; + T6LMP _ = x[1]; + T6LMP a = x[1]; assertEquals(a, y); assertEquals(x[0], "cell"); return "match 3"; @@ -1136,19 +1139,19 @@ function listMatchPattern39(T7 x, T6 y) returns string { } } -public type T8 ["list", T8, T8[]]|["list", T8[]]|"int"; +public type T8LMP ["list", T8LMP, T8LMP[]]|["list", T8LMP[]]|"int"; function testListMatchPattern40() { - T8 t1 = "int"; - T8[] t2 = ["int", "int", "int"]; - T8[] t3 = [t1]; - T8 t4 = ["list", ["int", "int", "int"]]; + T8LMP t1 = "int"; + T8LMP[] t2 = ["int", "int", "int"]; + T8LMP[] t3 = [t1]; + T8LMP t4 = ["list", ["int", "int", "int"]]; - T8 x1 = ["list", t3]; - T8 x2 = ["list", ["int", "int"]]; - T8 x3 = ["list", t2]; - T8 x4 = ["list", "int", t2]; - T8 x5 = ["list", t4, t3]; + T8LMP x1 = ["list", t3]; + T8LMP x2 = ["list", ["int", "int"]]; + T8LMP x3 = ["list", t2]; + T8LMP x4 = ["list", "int", t2]; + T8LMP x5 = ["list", t4, t3]; assertEquals(listMatchPattern40(x1, (), t3, ()), "match 4"); assertEquals(listMatchPattern40("int", (), (), ()), "match 1"); @@ -1158,7 +1161,7 @@ function testListMatchPattern40() { assertEquals(listMatchPattern40(x5, t4, t3, ()), "match 6"); } -function listMatchPattern40(T8 x, T8? t1, T8[]? t2, T8? t3) returns string? { +function listMatchPattern40(T8LMP x, T8LMP? t1, T8LMP[]? t2, T8LMP? t3) returns string? { match x { "int" => { return "match 1"; @@ -1177,7 +1180,7 @@ function listMatchPattern40(T8 x, T8? t1, T8[]? t2, T8? t3) returns string? { return "match 5"; } ["list", var y, var z] => { - T8 _ = z[0]; + T8LMP _ = z[0]; assertEquals(y, t1); assertEquals(z, t2); return "match 6"; @@ -1188,15 +1191,15 @@ function listMatchPattern40(T8 x, T8? t1, T8[]? t2, T8? t3) returns string? { } } -public type T9 ["array", T9]|["cell", T6]|["array", T6]|[string, int]; +public type T9LMP ["array", T9LMP]|["cell", T6LMP]|["array", T6LMP]|[string, int]; function testListMatchPattern41() { - T9 x1 = ["cell", "int"]; - T9 x2 = ["array", ["|", "int"]]; - T9 x3 = ["cell", ["|", "int"]]; - T9 x4 = ["string 1", 1]; - T9 x5 = ["array", x4]; - T9 x6 = ["string 2", 1]; + T9LMP x1 = ["cell", "int"]; + T9LMP x2 = ["array", ["|", "int"]]; + T9LMP x3 = ["cell", ["|", "int"]]; + T9LMP x4 = ["string 1", 1]; + T9LMP x5 = ["array", x4]; + T9LMP x6 = ["string 2", 1]; assertEquals(listMatchPattern41(x1), "match 1"); assertEquals(listMatchPattern41(x2), "match 4"); @@ -1206,7 +1209,7 @@ function testListMatchPattern41() { assertEquals(listMatchPattern41(x6), "match 6"); } -function listMatchPattern41(T9 x) returns string { +function listMatchPattern41(T9LMP x) returns string { match x { ["cell", "int"] => { return "match 1"; @@ -1229,14 +1232,14 @@ function listMatchPattern41(T9 x) returns string { } } -public type T10 [string, decimal, string]|[string, boolean...]|[int...]|[boolean]; +public type T10LMP [string, decimal, string]|[string, boolean...]|[int...]|[boolean]; function testListMatchPattern42() { - T10 x1 = ["string", 1d, "string"]; - T10 x2 = ["string", true, true, true, true, true, true]; - T10 x3 = [1, 1, 1, 1]; - T10 x4 = [true]; - T10 x5 = ["string", true]; + T10LMP x1 = ["string", 1d, "string"]; + T10LMP x2 = ["string", true, true, true, true, true, true]; + T10LMP x3 = [1, 1, 1, 1]; + T10LMP x4 = [true]; + T10LMP x5 = ["string", true]; assertEquals(listMatchPattern42(x1, ["string", 1d, "string"]), "match 1"); assertEquals(listMatchPattern42(x2, ["string", true, true, true, true, [true, true]]), "match 3"); @@ -1245,7 +1248,7 @@ function testListMatchPattern42() { assertEquals(listMatchPattern42(x5, ["string", true]), "match 5"); } -function listMatchPattern42(T10 t, anydata a) returns string { +function listMatchPattern42(T10LMP t, anydata a) returns string { match t { [var x, var y, var z] => { assertEquals([x, y, z], a); @@ -1270,25 +1273,25 @@ function listMatchPattern42(T10 t, anydata a) returns string { } } -public type T11 [int, T11, T11...]|[T11...]|["int"]; +public type T11LMP [int, T11LMP, T11LMP...]|[T11LMP...]|["int"]; public function testListMatchPattern43() { - T11[] t1 = [["int"], ["int"], ["int"]]; - T11 x1 = [1, ["int"], ["int"]]; - T11 x2 = [1, ["int"], ["int"], ["int"], ["int"], ["int"], ["int"], ["int"]]; - T11 x3 = [["int"], ["int"], ["int"], ["int"]]; - T11 x4 = [["int"]]; - T11 x5 = [t1, ["int"]]; + T11LMP[] t1 = [["int"], ["int"], ["int"]]; + T11LMP x1 = [1, ["int"], ["int"]]; + T11LMP x2 = [1, ["int"], ["int"], ["int"], ["int"], ["int"], ["int"], ["int"]]; + T11LMP x3 = [["int"], ["int"], ["int"], ["int"]]; + T11LMP x4 = [["int"]]; + T11LMP x5 = [t1, ["int"]]; assertEquals(listMatchPattern43(x1, [1, ["int"], ["int"]]), "match 1"); assertEquals(listMatchPattern43(x2, - [1, ["int"], ["int"], ["int"], ["int"], [["int"], ["int"], ["int"]]]), "match 3"); + [1, ["int"], ["int"], ["int"], ["int"], [["int"], ["int"], ["int"]]]), "match 3"); assertEquals(listMatchPattern43(x3, [["int"], ["int"], ["int"], [["int"]]]), "match 4"); assertEquals(listMatchPattern43(x4, [["int"]]), "match 2"); assertEquals(listMatchPattern43(x5, [t1, ["int"]]), "match 5"); } -function listMatchPattern43(T11 t, anydata a) returns string { +function listMatchPattern43(T11LMP t, anydata a) returns string { match t { [var x, var y, var z] => { assertEquals([x, y, z], a); @@ -1313,23 +1316,24 @@ function listMatchPattern43(T11 t, anydata a) returns string { } } -public type T12 [int, T12[], T12...]|[T12[]...]|"int"; -public type T13 [int, T13, T13, T13[]...]|[T13...]|"int"; +public type T12LMP [int, T12LMP[], T12LMP...]|[T12LMP[]...]|"int"; + +public type T13LMP [int, T13LMP, T13LMP, T13LMP[]...]|[T13LMP...]|"int"; public function testListMatchPattern44() { - T12[] t1 = ["int", "int", "int"]; - T12 x1 = [1, t1, "int", "int"]; - T12 x2 = [1, t1, "int", "int", "int", "int", "int", "int", "int"]; - T12 x3 = [t1, t1, t1, t1, t1]; - T12 x4 = [t1]; - T12 x5 = [t1, t1]; - - T13[] t2 = ["int", "int", "int"]; - T13 y1 = [1, "int", "int", t2, t2]; - T13 y2 = [1, "int", "int", t2, t2, t2, t2, t2, t2, t2]; - T13 y3 = [t2, t2, t2, t2, t2, t2]; - T13 y4 = [t2]; - T13 y5 = [t2, t2]; + T12LMP[] t1 = ["int", "int", "int"]; + T12LMP x1 = [1, t1, "int", "int"]; + T12LMP x2 = [1, t1, "int", "int", "int", "int", "int", "int", "int"]; + T12LMP x3 = [t1, t1, t1, t1, t1]; + T12LMP x4 = [t1]; + T12LMP x5 = [t1, t1]; + + T13LMP[] t2 = ["int", "int", "int"]; + T13LMP y1 = [1, "int", "int", t2, t2]; + T13LMP y2 = [1, "int", "int", t2, t2, t2, t2, t2, t2, t2]; + T13LMP y3 = [t2, t2, t2, t2, t2, t2]; + T13LMP y4 = [t2]; + T13LMP y5 = [t2, t2]; assertEquals(listMatchPattern44(x1, [1, t1, "int", "int"]), "match 1"); assertEquals(listMatchPattern44(x2, [1, t1, "int", "int", "int", "int", ["int", "int", "int"]]), "match 3"); @@ -1344,8 +1348,8 @@ public function testListMatchPattern44() { assertEquals(listMatchPattern44(y5, [t2, t2]), "match 5"); } -function listMatchPattern44(T12|T13 t, anydata a) returns string? { - if t is T12 { +function listMatchPattern44(T12LMP|T13LMP t, anydata a) returns string? { + if t is T12LMP { match t { [var p, var x, var y, var z] => { assertEquals([p, x, y, z], a); @@ -1394,18 +1398,19 @@ function listMatchPattern44(T12|T13 t, anydata a) returns string? { } } -public type T14 [string]|[int, string]|[int, int, string]; -public type T15 [int]|[T15, T15]|[T15[], T15[], T15[]]; +public type T14LMP [string]|[int, string]|[int, int, string]; + +public type T15LMP [int]|[T15LMP, T15LMP]|[T15LMP[], T15LMP[], T15LMP[]]; public function testListMatchPattern45() { - T14 x1 = ["string"]; - T14 x2 = [1, "string"]; - T14 x3 = [1, 1, "string"]; + T14LMP x1 = ["string"]; + T14LMP x2 = [1, "string"]; + T14LMP x3 = [1, 1, "string"]; - T15 y1 = [1]; - T15[] t2 = [y1, y1]; - T15 y2 = [y1, y1]; - T15 y3 = [t2, t2, t2]; + T15LMP y1 = [1]; + T15LMP[] t2 = [y1, y1]; + T15LMP y2 = [y1, y1]; + T15LMP y3 = [t2, t2, t2]; assertEquals(listMatchPattern45(x1, x1), "match 3"); assertEquals(listMatchPattern45(x2, x2), "match 2"); @@ -1416,7 +1421,7 @@ public function testListMatchPattern45() { assertEquals(listMatchPattern45(y3, y3), "match 1"); } -function listMatchPattern45(T14|T15 t, anydata a) returns string? { +function listMatchPattern45(T14LMP|T15LMP t, anydata a) returns string? { match t { [var p, var x, var y, ...var z] => { assertEquals([p, x, y], a); @@ -1436,14 +1441,14 @@ function listMatchPattern45(T14|T15 t, anydata a) returns string? { } } -public type T16 [string, int]; +public type T16LMP [string, int]; public function testListMatchPattern46() { assertEquals(listMatchPattern46(), "string"); } public function listMatchPattern46() returns string { - T16 a = ["string", 1]; + T16LMP a = ["string", 1]; string b; match a { [_, var x] => { @@ -1453,17 +1458,20 @@ public function listMatchPattern46() returns string { return b; } -type Data string|Data[]; -type Data2 ["call", string, Data...]; -type Data3 ["branch", string]; -type Data4 Data2|Data3; +type DataLMP string|DataLMP[]; + +type Data2LMP ["call", string, DataLMP...]; + +type Data3LMP ["branch", string]; + +type Data4LMP Data2LMP|Data3LMP; public function testListMatchPattern47() { assertEquals(listMatchPattern47(["branch", "b.target"]), "match 2"); assertEquals(listMatchPattern47(["call", "add", "1", "2"]), "match 1"); } -function listMatchPattern47(Data4 d) returns string { +function listMatchPattern47(Data4LMP d) returns string { match d { ["call", "add", ...var operands] => { return "match 1"; diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/match-stmt-type-narrow.bal b/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/match-stmt-type-narrow.bal index fb4d64647048..2c57d02fd94a 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/match-stmt-type-narrow.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/match-stmt-type-narrow.bal @@ -226,19 +226,19 @@ function testMatchClauseWithTypeGuard6() { assertEquals("Int or String", matched); } -type Person record {| +type PersonMtStmt record {| string name; int age; - Address address; + AddressMtStmt address; |}; -type Address record { +type AddressMtStmt record { string street; int houseNo; string city; }; -type AddressWithProvince record { +type AddressWithProvinceMtStmt record { string street; int houseNo; string city; @@ -248,20 +248,20 @@ type AddressWithProvince record { type E 100|200; function testMatchClauseWithTypeGuard7() { - Person person = {name: "John", age: 12, address: {street: "Main Street", houseNo:10, city: "Colombo", + PersonMtStmt person = {name: "John", age: 12, address: {street: "Main Street", houseNo:10, city: "Colombo", "country": "Sri Lanka"}}; string matched = "Not Matched"; int age = 0; string street = ""; - Person newPerson = person; + PersonMtStmt newPerson = person; match person { - {name: var a, ...var b} if a is string && b is record {|int age; AddressWithProvince address;|} => { + {name: var a, ...var b} if a is string && b is record {|int age; AddressWithProvinceMtStmt address;|} => { matched = "Pattern1"; age = b.age; street = b.address.street; } - {name: var a, ...var b} if a is string && b is record {|int age; Address address;|} => { + {name: var a, ...var b} if a is string && b is record {|int age; AddressMtStmt address;|} => { matched = "Pattern2"; age = b.age; street = b.address.street; diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/structured_record_match_patterns.bal b/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/structured_record_match_patterns.bal index dcdad44551b9..b37e6d5fabab 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/structured_record_match_patterns.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/statements/matchstmt/structured_record_match_patterns.bal @@ -14,14 +14,14 @@ // specific language governing permissions and limitations // under the License. -type Foo record { +type FooSRMP record { string s; int i; float f; }; function testStructuredMatchPatternsBasic1() returns string { - Foo foo = {s: "S", i: 23, f: 5.6}; + FooSRMP foo = {s: "S", i: 23, f: 5.6}; match foo { var {s, i: integer, f} => { @@ -32,14 +32,14 @@ function testStructuredMatchPatternsBasic1() returns string { return "Default"; } -type Bar record { +type BarSRMP record { byte b; - Foo f; + FooSRMP f; }; function testStructuredMatchPatternsBasic2() returns string { - Foo foo = {s: "S", i: 23, f: 5.6}; - Bar bar = {b: 12, f: foo}; + FooSRMP foo = {s: "S", i: 23, f: 5.6}; + BarSRMP bar = {b: 12, f: foo}; match bar { var {b: byteValue, f: {s, i, f}} => { @@ -52,8 +52,8 @@ function testStructuredMatchPatternsBasic2() returns string { } function testStructuredMatchPatternsBasic3() returns string { - Foo foo = {s: "S", i: 23, f: 5.6}; - Bar bar = {b: 12, f: foo}; + FooSRMP foo = {s: "S", i: 23, f: 5.6}; + BarSRMP bar = {b: 12, f: foo}; match bar { var {b, f} => { @@ -65,8 +65,8 @@ function testStructuredMatchPatternsBasic3() returns string { } function testStructuredMatchPatternsBasic4() returns string { - Foo foo = {s: "S", i: 23, f: 5.6}; - Bar bar = {b: 12, f: foo}; + FooSRMP foo = {s: "S", i: 23, f: 5.6}; + BarSRMP bar = {b: 12, f: foo}; match bar { var {a} => { @@ -105,17 +105,17 @@ function testStructuredMatchPatternsBasics5() returns string[] { ClosedFoo3 foo3 = {var1: "Hello", var2: 150, var3: true}; ClosedFoo4 foo4 = {var1: "Hello"}; - ClosedFoo1 | ClosedFoo2 | ClosedFoo3 | ClosedFoo4 a1 = foo1; - ClosedFoo1 | ClosedFoo2 | ClosedFoo3 | ClosedFoo4 a2 = foo2; - ClosedFoo1 | ClosedFoo2 | ClosedFoo3 | ClosedFoo4 a3 = foo3; - ClosedFoo1 | ClosedFoo2 | ClosedFoo3 | ClosedFoo4 a4 = foo4; + ClosedFoo1|ClosedFoo2|ClosedFoo3|ClosedFoo4 a1 = foo1; + ClosedFoo1|ClosedFoo2|ClosedFoo3|ClosedFoo4 a2 = foo2; + ClosedFoo1|ClosedFoo2|ClosedFoo3|ClosedFoo4 a3 = foo3; + ClosedFoo1|ClosedFoo2|ClosedFoo3|ClosedFoo4 a4 = foo4; string[] result = [basicMatch(a1), basicMatch(a2), basicMatch(a3), basicMatch(a4)]; return result; } -function basicMatch(ClosedFoo1 | ClosedFoo2 | ClosedFoo3 | ClosedFoo4 a) returns string { +function basicMatch(ClosedFoo1|ClosedFoo2|ClosedFoo3|ClosedFoo4 a) returns string { match a { var {var1, var2, var3} => { return "Matched with three vars : " + var1.toString() + ", " + @@ -146,16 +146,16 @@ function testStructuredMatchPatternComplex1() returns string[] { ClosedBar1 bar1 = {var1: "Ballerina", var2: 500}; ClosedBar2 bar2 = {var1: "Language", var2: bar1}; - ClosedBar1 | ClosedBar2 | string a1 = bar1; - ClosedBar1 | ClosedBar2 | string a2 = bar2; - ClosedBar1 | ClosedBar2 | string a3 = "bar2"; + ClosedBar1|ClosedBar2|string a1 = bar1; + ClosedBar1|ClosedBar2|string a2 = bar2; + ClosedBar1|ClosedBar2|string a3 = "bar2"; string[] result = [complexMatch(a1), complexMatch(a2), complexMatch(a3)]; return result; } -function complexMatch(ClosedBar1 | ClosedBar2 | string a) returns string { +function complexMatch(ClosedBar1|ClosedBar2|string a) returns string { match a { var {var1, var2: {var1: v1, var2}} => { return "Matched with three vars : " + var1.toString() + ", " + v1.toString() + @@ -172,16 +172,22 @@ function complexMatch(ClosedBar1 | ClosedBar2 | string a) returns string { function testRuntimeCheck() returns string[] { [int, boolean] tuple = [50, true]; - Foo foo1 = {s: "S", i: 23, f: 5.6, "t": tuple}; - Foo foo2 = {s: "S", i: 23, f: 5.6}; - Foo foo3 = {s: "S", i: 23, f: 5.6, "t": 12}; - - string[] values = [matchRuntimeCheck(foo1), matchRuntimeCheck(foo2), matchRuntimeCheck(foo3), - matchRuntimeCheckWithAny(foo1), matchRuntimeCheckWithAny(foo2), matchRuntimeCheckWithAny(foo3)]; + FooSRMP foo1 = {s: "S", i: 23, f: 5.6, "t": tuple}; + FooSRMP foo2 = {s: "S", i: 23, f: 5.6}; + FooSRMP foo3 = {s: "S", i: 23, f: 5.6, "t": 12}; + + string[] values = [ + matchRuntimeCheck(foo1), + matchRuntimeCheck(foo2), + matchRuntimeCheck(foo3), + matchRuntimeCheckWithAny(foo1), + matchRuntimeCheckWithAny(foo2), + matchRuntimeCheckWithAny(foo3) + ]; return values; } -function matchRuntimeCheck(Foo foo) returns string { +function matchRuntimeCheck(FooSRMP foo) returns string { match foo { var {s, i, f, t: [i2, b]} => { return "Matched with five vars : " + s.toString() + ", " + i.toString() + diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_selectively_immutable_type.bal b/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_selectively_immutable_type.bal index 89489482b63d..459d0ee84059 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_selectively_immutable_type.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/types/readonly/test_selectively_immutable_type.bal @@ -64,12 +64,12 @@ function testSimpleInitializationForSelectivelyImmutableXmlTypes() { assertEquality(c, r4); } -type Employee record {| - Details details; +type EmployeeFoo record {| + DetailsBar details; string department; |}; -type Details record {| +type DetailsBar record {| string name; int id; |}; @@ -78,9 +78,9 @@ function testSimpleInitializationForSelectivelyImmutableListTypes() { int[] & readonly a = [1, 2]; readonly r1 = a; assertTrue(r1 is int[] & readonly); - assertEquality( [1, 2], r1); + assertEquality([1, 2], r1); - Employee & readonly emp = { + EmployeeFoo & readonly emp = { details: { name: "Emma", id: 1234 @@ -88,60 +88,60 @@ function testSimpleInitializationForSelectivelyImmutableListTypes() { department: "finance" }; - [Employee, Employee] & readonly b = [emp, {details: {name: "Jo", id: 5678}, department: "IT"}]; + [EmployeeFoo, EmployeeFoo] & readonly b = [emp, {details: {name: "Jo", id: 5678}, department: "IT"}]; readonly r2 = b; - assertTrue(r2 is [Employee, Employee] & readonly); - assertTrue(r2 is Employee[2] & readonly); + assertTrue(r2 is [EmployeeFoo, EmployeeFoo] & readonly); + assertTrue(r2 is EmployeeFoo[2] & readonly); - [Employee, Employee] & readonly empTup = <[Employee, Employee] & readonly> checkpanic r2; + [EmployeeFoo, EmployeeFoo] & readonly empTup = <[EmployeeFoo, EmployeeFoo] & readonly>checkpanic r2; assertEquality(emp, empTup[0]); record {} rec = empTup[0]; - assertTrue(rec is Employee & readonly); + assertTrue(rec is EmployeeFoo & readonly); assertTrue(rec.isReadOnly()); rec = empTup[1]; - assertTrue(rec is Employee & readonly); + assertTrue(rec is EmployeeFoo & readonly); assertTrue(rec.isReadOnly()); assertEquality("IT", rec["department"]); - assertTrue(rec["details"] is Details & readonly); + assertTrue(rec["details"] is DetailsBar & readonly); assertTrue(rec["details"].isReadOnly()); - Details & readonly details = { + DetailsBar & readonly details = { name: "Jo", id: 9876 }; - [Details[], Employee...] & readonly detEmpTup = [ - [{name: "May", id: 1234}, details], - {details, department: "finance"} - ]; + [DetailsBar[], EmployeeFoo...] & readonly detEmpTup = [ + [{name: "May", id: 1234}, details], + {details, department: "finance"} + ]; readonly r3 = detEmpTup; - assertTrue(r3 is [Details[], Employee...] & readonly); - assertTrue(r3 is [[Details, Details], Employee] & readonly); + assertTrue(r3 is [DetailsBar[], EmployeeFoo...] & readonly); + assertTrue(r3 is [[DetailsBar, DetailsBar], EmployeeFoo] & readonly); - [[Details, Details], Employee] & readonly vals = <[[Details, Details], Employee] & readonly> checkpanic r3; + [[DetailsBar, DetailsBar], EmployeeFoo] & readonly vals = <[[DetailsBar, DetailsBar], EmployeeFoo] & readonly>checkpanic r3; assertTrue(vals[0].isReadOnly()); - Details d1 = vals[0][0]; - assertEquality(
{name: "May", id: 1234}, d1); + DetailsBar d1 = vals[0][0]; + assertEquality({name: "May", id: 1234}, d1); assertTrue(d1.isReadOnly()); - Details d2 = vals[0][1]; + DetailsBar d2 = vals[0][1]; assertEquality(details, d2); assertTrue(d2.isReadOnly()); - Employee e = vals[1]; - assertEquality( {details, department: "finance"}, e); + EmployeeFoo e = vals[1]; + assertEquality({details, department: "finance"}, e); assertTrue(e.isReadOnly()); assertTrue(e.details.isReadOnly()); (int[] & readonly)|string[] arr = [1, 2]; - assertEquality( [1, 2], arr); + assertEquality([1, 2], arr); assertTrue(arr is int[] & readonly); assertTrue(arr.isReadOnly()); arr = ["hello"]; - assertEquality( ["hello"], arr); + assertEquality(["hello"], arr); assertTrue(arr is string[]); assertFalse(arr is string[] & readonly); assertFalse(arr.isReadOnly()); @@ -157,9 +157,9 @@ function testSimpleInitializationForSelectivelyImmutableMappingTypes() { }; readonly r1 = a; assertTrue(r1 is map & readonly); - assertEquality(> {a: true, bool: false, c: false}, r1); + assertEquality(>{a: true, bool: false, c: false}, r1); - Employee & readonly emp = { + EmployeeFoo & readonly emp = { details: { name: "Emma", id: 1234 @@ -168,20 +168,20 @@ function testSimpleInitializationForSelectivelyImmutableMappingTypes() { }; readonly r2 = emp; - assertTrue(r2 is Employee & readonly); + assertTrue(r2 is EmployeeFoo & readonly); assertEquality(emp, r2); any|error val = r2; assertFalse(val is error); - Employee rec = checkpanic val; - assertTrue(rec is Employee & readonly); + EmployeeFoo rec = checkpanic val; + assertTrue(rec is EmployeeFoo & readonly); assertTrue(rec.isReadOnly()); - Details det = rec.details; - assertTrue(det is Details & readonly); + DetailsBar det = rec.details; + assertTrue(det is DetailsBar & readonly); assertTrue(det.isReadOnly()); - Student & readonly st = { + StudentFoo & readonly st = { details: { name: "Jo", id: 4567 @@ -190,25 +190,25 @@ function testSimpleInitializationForSelectivelyImmutableMappingTypes() { "science": ["P", 65] }; readonly r3 = st; - assertTrue(r3 is Student & readonly); + assertTrue(r3 is StudentFoo & readonly); val = r3; assertFalse(val is error); - Student stVal = checkpanic val; + StudentFoo stVal = checkpanic val; assertTrue(stVal.isReadOnly()); assertTrue(stVal.details.isReadOnly()); - assertEquality(
{name: "Jo", id: 4567}, stVal.details); + assertEquality({name: "Jo", id: 4567}, stVal.details); - assertTrue(stVal["math"] is [RESULT, int] & readonly); + assertTrue(stVal["math"] is [RESULTFoo, int] & readonly); assertTrue(stVal["math"].isReadOnly()); - assertEquality(<[RESULT, int]> ["P", 75], stVal["math"]); + assertEquality(<[RESULTFoo, int]>["P", 75], stVal["math"]); - assertTrue(stVal["science"] is [RESULT, int] & readonly); + assertTrue(stVal["science"] is [RESULTFoo, int] & readonly); assertTrue(stVal["science"].isReadOnly()); - assertEquality(<[RESULT, int]> ["P", 65], stVal["science"]); + assertEquality(<[RESULTFoo, int]>["P", 65], stVal["science"]); } -type Identifier record {| +type IdentifierFoo record {| readonly string name; int id; |}; @@ -222,41 +222,41 @@ function testSimpleInitializationForSelectivelyImmutableTableTypes() { readonly r1 = a; assertTrue(r1 is table> & readonly); - table> tbString = >> checkpanic r1; + table> tbString = >>checkpanic r1; assertEquality(2, tbString.length()); map[] mapArr = tbString.toArray(); - assertTrue( mapArr[0] is map & readonly); - assertEquality(> {x: "x", y: "y"}, mapArr[0]); - assertTrue( mapArr[1] is map & readonly); - assertEquality(> {z: "z"}, mapArr[1]); + assertTrue(mapArr[0] is map & readonly); + assertEquality(>{x: "x", y: "y"}, mapArr[0]); + assertTrue(mapArr[1] is map & readonly); + assertEquality(>{z: "z"}, mapArr[1]); - table key(name) & readonly b = table [ + table key(name) & readonly b = table [ {name: "Jo", id: 4567}, {name: "Emma", id: 1234}, {name: "Amy", id: 678} ]; readonly r2 = b; - assertTrue(r2 is table key(name) & readonly); - assertTrue(r2 is table & readonly); + assertTrue(r2 is table key(name) & readonly); + assertTrue(r2 is table & readonly); - table tbDetails = > checkpanic r2; + table tbDetails = >checkpanic r2; assertEquality(3, tbDetails.length()); - Identifier[] detailsArr = tbDetails.toArray(); - assertTrue(detailsArr[0] is Identifier & readonly); - assertEquality( {name: "Jo", id: 4567}, detailsArr[0]); - assertTrue(detailsArr[1] is Identifier & readonly); - assertEquality( {name: "Emma", id: 1234}, detailsArr[1]); - assertTrue(detailsArr[2] is Identifier & readonly); - assertEquality( {name: "Amy", id: 678}, detailsArr[2]); + IdentifierFoo[] detailsArr = tbDetails.toArray(); + assertTrue(detailsArr[0] is IdentifierFoo & readonly); + assertEquality({name: "Jo", id: 4567}, detailsArr[0]); + assertTrue(detailsArr[1] is IdentifierFoo & readonly); + assertEquality({name: "Emma", id: 1234}, detailsArr[1]); + assertTrue(detailsArr[2] is IdentifierFoo & readonly); + assertEquality({name: "Amy", id: 678}, detailsArr[2]); } -type RESULT "P"|"F"; +type RESULTFoo "P"|"F"; -type Student record {| - Details details; - [RESULT, int]...; +type StudentFoo record {| + DetailsBar details; + [RESULTFoo, int]...; |}; type recursiveTuple [int, string|xml, recursiveTuple...]; @@ -296,7 +296,7 @@ function testRuntimeIsTypeForSelectivelyImmutableBasicTypes() { assertFalse(k is readonly); assertTrue(l is readonly); - Employee m = { + EmployeeFoo m = { details: { name: "Maryam", id: 9876 @@ -317,7 +317,7 @@ function testRuntimeIsTypeNegativeForSelectivelyImmutableTypes() { assertFalse(an1 is readonly); assertFalse(a1.isReadOnly()); - Employee emp = { + EmployeeFoo emp = { details: { name: "Emma", id: 1234 @@ -325,54 +325,54 @@ function testRuntimeIsTypeNegativeForSelectivelyImmutableTypes() { department: "finance" }; - [Employee, Employee] b = [emp, {details: {name: "Jo", id: 5678}, department: "IT"}]; + [EmployeeFoo, EmployeeFoo] b = [emp, {details: {name: "Jo", id: 5678}, department: "IT"}]; anydata a2 = b; any an2 = b; - assertFalse(an2 is [Employee, Employee] & readonly); + assertFalse(an2 is [EmployeeFoo, EmployeeFoo] & readonly); assertFalse(an2 is readonly); assertFalse(a2.isReadOnly()); - [Employee, Employee] empTup = <[Employee, Employee]> a2; + [EmployeeFoo, EmployeeFoo] empTup = <[EmployeeFoo, EmployeeFoo]>a2; assertEquality(emp, empTup[0]); record {} rec = empTup[0]; - assertTrue(rec is Employee); - assertFalse(rec is Employee & readonly); + assertTrue(rec is EmployeeFoo); + assertFalse(rec is EmployeeFoo & readonly); assertFalse(rec.isReadOnly()); rec = empTup[1]; - assertTrue(rec is Employee); - assertFalse(rec is Employee & readonly); + assertTrue(rec is EmployeeFoo); + assertFalse(rec is EmployeeFoo & readonly); assertFalse(rec.isReadOnly()); - assertTrue(rec["details"] is Details); - assertFalse(rec["details"] is Details & readonly); + assertTrue(rec["details"] is DetailsBar); + assertFalse(rec["details"] is DetailsBar & readonly); assertFalse(rec["details"].isReadOnly()); - Details & readonly details = { + DetailsBar & readonly details = { name: "Jo", id: 9876 }; - [Details[], Employee...] detEmpTup = [ - [{name: "May", id: 1234}, details], - {details, department: "finance"} - ]; + [DetailsBar[], EmployeeFoo...] detEmpTup = [ + [{name: "May", id: 1234}, details], + {details, department: "finance"} + ]; anydata a3 = detEmpTup; - assertTrue(a3 is [Details[], Employee...]); - assertFalse(a3 is [Details[], Employee...] & readonly); - assertFalse(a3 is [[Details, Details], Employee] & readonly); + assertTrue(a3 is [DetailsBar[], EmployeeFoo...]); + assertFalse(a3 is [DetailsBar[], EmployeeFoo...] & readonly); + assertFalse(a3 is [[DetailsBar, DetailsBar], EmployeeFoo] & readonly); - [Details[], Employee...] vals = <[Details[], Employee...]> a3; + [DetailsBar[], EmployeeFoo...] vals = <[DetailsBar[], EmployeeFoo...]>a3; assertFalse(vals[0].isReadOnly()); - Details d1 = vals[0][0]; + DetailsBar d1 = vals[0][0]; assertFalse(d1.isReadOnly()); - Details d2 = vals[0][1]; + DetailsBar d2 = vals[0][1]; assertEquality(details, d2); assertTrue(d2.isReadOnly()); - Employee e = vals[1]; - assertEquality( {details, department: "finance"}, e); + EmployeeFoo e = vals[1]; + assertEquality({details, department: "finance"}, e); assertFalse(e.isReadOnly()); assertTrue(e.details.isReadOnly()); @@ -396,8 +396,8 @@ function testRuntimeIsTypeNegativeForSelectivelyImmutableTypes() { assertFalse(an5 is readonly); assertFalse(a5.isReadOnly()); - json[] jsonVal = an5; - map a8 = > jsonVal[1]; + json[] jsonVal = an5; + map a8 = >jsonVal[1]; any an8 = a8; assertFalse(a8 is map & readonly); assertFalse(an8 is readonly); @@ -421,15 +421,15 @@ function testRuntimeIsTypeNegativeForSelectivelyImmutableTypes() { assertFalse(an7 is readonly); assertFalse(a7.isReadOnly()); - table key(name) j = table [ + table key(name) j = table [ {name: "Jo", id: 4567}, {name: "Emma", id: 1234}, {name: "Amy", id: 678} ]; anydata a9 = j; any an9 = j; - assertTrue(an9 is table); - assertFalse(an9 is table & readonly); + assertTrue(an9 is table); + assertFalse(an9 is table & readonly); assertFalse(an9 is readonly); assertFalse(a9.isReadOnly());