goscript 0.0.21 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -724,12 +724,29 @@ export interface BaseTypeInfo {
724
724
  zeroValue?: any
725
725
  }
726
726
 
727
+ /**
728
+ * Represents an argument or a return value of a method.
729
+ */
730
+ export interface MethodArg {
731
+ name?: string; // Name of the argument/return value, if available
732
+ type: TypeInfo | string; // TypeInfo object or string name of the type
733
+ }
734
+
735
+ /**
736
+ * Represents the signature of a method, including its name, arguments, and return types.
737
+ */
738
+ export interface MethodSignature {
739
+ name: string;
740
+ args: MethodArg[];
741
+ returns: MethodArg[];
742
+ }
743
+
727
744
  /**
728
745
  * Type information for struct types
729
746
  */
730
747
  export interface StructTypeInfo extends BaseTypeInfo {
731
748
  kind: TypeKind.Struct
732
- methods: Set<string>
749
+ methods: MethodSignature[] // Array of method signatures
733
750
  ctor?: new (...args: any[]) => any
734
751
  fields: Record<string, TypeInfo | string> // Field names and types for struct fields
735
752
  }
@@ -739,7 +756,7 @@ export interface StructTypeInfo extends BaseTypeInfo {
739
756
  */
740
757
  export interface InterfaceTypeInfo extends BaseTypeInfo {
741
758
  kind: TypeKind.Interface
742
- methods: Set<string>
759
+ methods: MethodSignature[] // Array of method signatures
743
760
  }
744
761
 
745
762
  /**
@@ -790,6 +807,7 @@ export interface FunctionTypeInfo extends BaseTypeInfo {
790
807
  kind: TypeKind.Function
791
808
  params?: (string | TypeInfo)[]
792
809
  results?: (string | TypeInfo)[]
810
+ isVariadic?: boolean // True if the function is variadic (e.g., ...T)
793
811
  }
794
812
 
795
813
  /**
@@ -860,14 +878,15 @@ const typeRegistry = new Map<string, TypeInfo>()
860
878
  *
861
879
  * @param name The name of the type.
862
880
  * @param zeroValue The zero value for the type.
863
- * @param methods Set of method names for the struct.
881
+ * @param methods Array of method signatures for the struct.
864
882
  * @param ctor Constructor for the struct.
883
+ * @param fields Record of field names and their types.
865
884
  * @returns The struct type information object.
866
885
  */
867
886
  export const registerStructType = (
868
887
  name: string,
869
888
  zeroValue: any,
870
- methods: Set<string>,
889
+ methods: MethodSignature[],
871
890
  ctor: new (...args: any[]) => any,
872
891
  fields: Record<string, TypeInfo | string> = {},
873
892
  ): StructTypeInfo => {
@@ -888,13 +907,13 @@ export const registerStructType = (
888
907
  *
889
908
  * @param name The name of the type.
890
909
  * @param zeroValue The zero value for the type (usually null).
891
- * @param methods Set of method names the interface requires.
910
+ * @param methods Array of method signatures for the interface.
892
911
  * @returns The interface type information object.
893
912
  */
894
913
  export const registerInterfaceType = (
895
914
  name: string,
896
915
  zeroValue: any,
897
- methods: Set<string>,
916
+ methods: MethodSignature[],
898
917
  ): InterfaceTypeInfo => {
899
918
  const typeInfo: InterfaceTypeInfo = {
900
919
  name,
@@ -935,6 +954,147 @@ function normalizeTypeInfo(info: string | TypeInfo): TypeInfo {
935
954
  return info
936
955
  }
937
956
 
957
+ function compareOptionalTypeInfo(
958
+ type1?: string | TypeInfo,
959
+ type2?: string | TypeInfo,
960
+ ): boolean {
961
+ if (type1 === undefined && type2 === undefined) return true
962
+ if (type1 === undefined || type2 === undefined) return false
963
+ // Assuming areTypeInfosIdentical will handle normalization if needed,
964
+ // but type1 and type2 here are expected to be direct fields from TypeInfo objects.
965
+ return areTypeInfosIdentical(type1, type2)
966
+ }
967
+
968
+ function areFuncParamOrResultArraysIdentical(
969
+ arr1?: (string | TypeInfo)[],
970
+ arr2?: (string | TypeInfo)[]
971
+ ): boolean {
972
+ if (arr1 === undefined && arr2 === undefined) return true
973
+ if (arr1 === undefined || arr2 === undefined) return false
974
+ if (arr1.length !== arr2.length) return false
975
+ for (let i = 0; i < arr1.length; i++) {
976
+ if (!areTypeInfosIdentical(arr1[i], arr2[i])) {
977
+ return false
978
+ }
979
+ }
980
+ return true
981
+ }
982
+
983
+ function areFuncSignaturesIdentical(
984
+ func1: FunctionTypeInfo,
985
+ func2: FunctionTypeInfo,
986
+ ): boolean {
987
+ if ((func1.isVariadic || false) !== (func2.isVariadic || false)) {
988
+ return false
989
+ }
990
+ return (
991
+ areFuncParamOrResultArraysIdentical(func1.params, func2.params) &&
992
+ areFuncParamOrResultArraysIdentical(func1.results, func2.results)
993
+ )
994
+ }
995
+
996
+ function areMethodArgsArraysIdentical(
997
+ args1?: MethodArg[],
998
+ args2?: MethodArg[],
999
+ ): boolean {
1000
+ if (args1 === undefined && args2 === undefined) return true
1001
+ if (args1 === undefined || args2 === undefined) return false
1002
+ if (args1.length !== args2.length) return false
1003
+ for (let i = 0; i < args1.length; i++) {
1004
+ // Compare based on type only, names of args/results don't affect signature identity here.
1005
+ if (!areTypeInfosIdentical(args1[i].type, args2[i].type)) {
1006
+ return false
1007
+ }
1008
+ }
1009
+ return true
1010
+ }
1011
+
1012
+ export function areTypeInfosIdentical(
1013
+ type1InfoOrName: string | TypeInfo,
1014
+ type2InfoOrName: string | TypeInfo,
1015
+ ): boolean {
1016
+ const t1Norm = normalizeTypeInfo(type1InfoOrName)
1017
+ const t2Norm = normalizeTypeInfo(type2InfoOrName)
1018
+
1019
+ if (t1Norm === t2Norm) return true // Object identity
1020
+ if (t1Norm.kind !== t2Norm.kind) return false
1021
+
1022
+ // If types have names, the names must match for identity.
1023
+ // If one has a name and the other doesn't, they are not identical.
1024
+ if (t1Norm.name !== t2Norm.name) return false
1025
+
1026
+ // If both are named and names match, for Basic, Struct, Interface, this is sufficient for identity.
1027
+ if (t1Norm.name !== undefined /* && t2Norm.name is also defined and equal */) {
1028
+ if (
1029
+ t1Norm.kind === TypeKind.Basic ||
1030
+ t1Norm.kind === TypeKind.Struct ||
1031
+ t1Norm.kind === TypeKind.Interface
1032
+ ) {
1033
+ return true
1034
+ }
1035
+ }
1036
+ // For other types (Pointer, Slice, etc.), or if both are anonymous (name is undefined),
1037
+ // structural comparison is needed.
1038
+
1039
+ switch (t1Norm.kind) {
1040
+ case TypeKind.Basic:
1041
+ // Names matched if they were defined, or both undefined (which means true by t1Norm.name !== t2Norm.name being false)
1042
+ return true
1043
+ case TypeKind.Pointer:
1044
+ return compareOptionalTypeInfo(
1045
+ (t1Norm as PointerTypeInfo).elemType,
1046
+ (t2Norm as PointerTypeInfo).elemType,
1047
+ )
1048
+ case TypeKind.Slice:
1049
+ return compareOptionalTypeInfo(
1050
+ (t1Norm as SliceTypeInfo).elemType,
1051
+ (t2Norm as SliceTypeInfo).elemType,
1052
+ )
1053
+ case TypeKind.Array:
1054
+ return (
1055
+ (t1Norm as ArrayTypeInfo).length === (t2Norm as ArrayTypeInfo).length &&
1056
+ compareOptionalTypeInfo(
1057
+ (t1Norm as ArrayTypeInfo).elemType,
1058
+ (t2Norm as ArrayTypeInfo).elemType,
1059
+ )
1060
+ )
1061
+ case TypeKind.Map:
1062
+ return (
1063
+ compareOptionalTypeInfo(
1064
+ (t1Norm as MapTypeInfo).keyType,
1065
+ (t2Norm as MapTypeInfo).keyType,
1066
+ ) &&
1067
+ compareOptionalTypeInfo(
1068
+ (t1Norm as MapTypeInfo).elemType,
1069
+ (t2Norm as MapTypeInfo).elemType,
1070
+ )
1071
+ )
1072
+ case TypeKind.Channel:
1073
+ return (
1074
+ // Ensure direction property exists before comparing, or handle undefined if it can be
1075
+ ((t1Norm as ChannelTypeInfo).direction || 'both') === ((t2Norm as ChannelTypeInfo).direction || 'both') &&
1076
+ compareOptionalTypeInfo(
1077
+ (t1Norm as ChannelTypeInfo).elemType,
1078
+ (t2Norm as ChannelTypeInfo).elemType,
1079
+ )
1080
+ )
1081
+ case TypeKind.Function:
1082
+ return areFuncSignaturesIdentical(
1083
+ t1Norm as FunctionTypeInfo,
1084
+ t2Norm as FunctionTypeInfo,
1085
+ )
1086
+ case TypeKind.Struct:
1087
+ case TypeKind.Interface:
1088
+ // If we reach here, names were undefined (both anonymous) or names matched but was not Basic/Struct/Interface.
1089
+ // For anonymous Struct/Interface, strict identity means full structural comparison.
1090
+ // For now, we consider anonymous types not identical unless they are the same object (caught above).
1091
+ // If they were named and matched, 'return true' was hit earlier for these kinds.
1092
+ return false
1093
+ default:
1094
+ return false
1095
+ }
1096
+ }
1097
+
938
1098
  /**
939
1099
  * Validates that a map key matches the expected type info.
940
1100
  *
@@ -993,16 +1153,20 @@ function matchesStructType(value: any, info: TypeInfo): boolean {
993
1153
  return true
994
1154
  }
995
1155
 
1156
+ // Check if the value has all methods defined in the struct's TypeInfo
1157
+ // This is a structural check, not a signature check here.
1158
+ // Signature checks are more relevant for interface satisfaction.
996
1159
  if (info.methods && typeof value === 'object' && value !== null) {
997
- const allMethodsMatch = Array.from(info.methods).every(
998
- (method) => typeof value[method] === 'function',
1160
+ const allMethodsExist = info.methods.every(
1161
+ (methodSig) => typeof (value as any)[methodSig.name] === 'function',
999
1162
  )
1000
- if (allMethodsMatch) {
1001
- return true
1163
+ if (!allMethodsExist) {
1164
+ return false
1002
1165
  }
1166
+ // Further signature checking could be added here if needed for struct-to-struct assignability
1003
1167
  }
1004
1168
 
1005
- if (typeof value === 'object' && value !== null) {
1169
+ if (typeof value === 'object' && value !== null && info.fields) {
1006
1170
  const fieldNames = Object.keys(info.fields || {})
1007
1171
  const valueFields = Object.keys(value)
1008
1172
 
@@ -1034,19 +1198,96 @@ function matchesStructType(value: any, info: TypeInfo): boolean {
1034
1198
  * @param info The interface type info to match against.
1035
1199
  * @returns True if the value matches the interface type, false otherwise.
1036
1200
  */
1201
+ /**
1202
+ * Checks if a value matches an interface type info by verifying it implements
1203
+ * all required methods with compatible signatures.
1204
+ *
1205
+ * @param value The value to check.
1206
+ * @param info The interface type info to match against.
1207
+ * @returns True if the value matches the interface type, false otherwise.
1208
+ */
1037
1209
  function matchesInterfaceType(value: any, info: TypeInfo): boolean {
1038
- // For interfaces, check if the value has all the required methods
1039
- if (
1040
- isInterfaceTypeInfo(info) &&
1041
- info.methods &&
1042
- typeof value === 'object' &&
1043
- value !== null
1044
- ) {
1045
- return Array.from(info.methods).every(
1046
- (method) => typeof (value as any)[method] === 'function',
1047
- )
1210
+ // Check basic conditions first
1211
+ if (!isInterfaceTypeInfo(info) || typeof value !== 'object' || value === null) {
1212
+ return false
1048
1213
  }
1049
- return false
1214
+
1215
+ // For interfaces, check if the value has all the required methods with compatible signatures
1216
+ return info.methods.every((requiredMethodSig) => {
1217
+ const actualMethod = (value as any)[requiredMethodSig.name]
1218
+
1219
+ // Method must exist and be a function
1220
+ if (typeof actualMethod !== 'function') {
1221
+ return false
1222
+ }
1223
+
1224
+ // Check parameter count (basic arity check)
1225
+ // Note: This is a simplified check as JavaScript functions can have optional/rest parameters
1226
+ const declaredParamCount = actualMethod.length
1227
+ const requiredParamCount = requiredMethodSig.args.length
1228
+
1229
+ // Strict arity checking can be problematic in JS, so we'll be lenient
1230
+ // A method with fewer params than required is definitely incompatible
1231
+ if (declaredParamCount < requiredParamCount) {
1232
+ return false
1233
+ }
1234
+
1235
+ // Check return types if we can determine them
1236
+ // This is challenging in JavaScript without runtime type information
1237
+
1238
+ // If the value has a __goTypeName property, it might be a registered type
1239
+ // with more type information available
1240
+ if (value.__goTypeName) {
1241
+ const valueTypeInfo = typeRegistry.get(value.__goTypeName)
1242
+ if (valueTypeInfo && isStructTypeInfo(valueTypeInfo)) {
1243
+ // Find the matching method in the value's type info
1244
+ const valueMethodSig = valueTypeInfo.methods.find(
1245
+ m => m.name === requiredMethodSig.name
1246
+ )
1247
+
1248
+ if (valueMethodSig) {
1249
+ // Compare return types
1250
+ if (valueMethodSig.returns.length !== requiredMethodSig.returns.length) {
1251
+ return false
1252
+ }
1253
+
1254
+ // Compare each return type for compatibility
1255
+ for (let i = 0; i < requiredMethodSig.returns.length; i++) {
1256
+ const requiredReturnType = normalizeTypeInfo(
1257
+ requiredMethodSig.returns[i].type
1258
+ )
1259
+ const valueReturnType = normalizeTypeInfo(
1260
+ valueMethodSig.returns[i].type
1261
+ )
1262
+
1263
+ // For interface return types, we need to check if the value's return type
1264
+ // implements the required interface
1265
+ if (isInterfaceTypeInfo(requiredReturnType)) {
1266
+ // This would be a recursive check, but we'll simplify for now
1267
+ // by just checking if the types are the same or if the value type
1268
+ // is registered as implementing the interface
1269
+ if (requiredReturnType.name !== valueReturnType.name) {
1270
+ // Check if valueReturnType implements requiredReturnType
1271
+ // This would require additional implementation tracking
1272
+ return false
1273
+ }
1274
+ }
1275
+ // For non-interface types, check direct type compatibility
1276
+ else if (requiredReturnType.name !== valueReturnType.name) {
1277
+ return false
1278
+ }
1279
+ }
1280
+
1281
+ // Similarly, we could check parameter types for compatibility
1282
+ // but we'll skip that for brevity
1283
+ }
1284
+ }
1285
+ }
1286
+
1287
+ // If we can't determine detailed type information, we'll accept the method
1288
+ // as long as it exists with a compatible arity
1289
+ return true
1290
+ })
1050
1291
  }
1051
1292
 
1052
1293
  /**
@@ -1297,22 +1538,46 @@ export function typeAssert<T>(
1297
1538
 
1298
1539
  if (
1299
1540
  isStructTypeInfo(normalizedType) &&
1300
- normalizedType.methods &&
1541
+ normalizedType.methods && normalizedType.methods.length > 0 &&
1301
1542
  typeof value === 'object' &&
1302
1543
  value !== null
1303
1544
  ) {
1304
- // Check if the value implements all methods of the struct type
1305
- const allMethodsMatch = Array.from(normalizedType.methods).every(
1306
- (method) => typeof value[method] === 'function',
1307
- )
1545
+ // Check if the value implements all methods of the struct type with compatible signatures.
1546
+ // This is more for interface satisfaction by a struct.
1547
+ // For struct-to-struct assertion, usually instanceof or field checks are primary.
1548
+ const allMethodsMatch = normalizedType.methods.every((requiredMethodSig) => {
1549
+ const actualMethod = (value as any)[requiredMethodSig.name];
1550
+ if (typeof actualMethod !== 'function') {
1551
+ return false;
1552
+ }
1553
+ const valueTypeInfoVal = (value as any).$typeInfo
1554
+ if (valueTypeInfoVal) {
1555
+ const normalizedValueType = normalizeTypeInfo(valueTypeInfoVal)
1556
+ if (isStructTypeInfo(normalizedValueType) || isInterfaceTypeInfo(normalizedValueType)) {
1557
+ const actualValueMethodSig = normalizedValueType.methods.find(m => m.name === requiredMethodSig.name)
1558
+ if (actualValueMethodSig) {
1559
+ // Perform full signature comparison using MethodSignatures
1560
+ const paramsMatch = areMethodArgsArraysIdentical(requiredMethodSig.args, actualValueMethodSig.args)
1561
+ const resultsMatch = areMethodArgsArraysIdentical(requiredMethodSig.returns, actualValueMethodSig.returns)
1562
+ return paramsMatch && resultsMatch
1563
+ } else {
1564
+ // Value has TypeInfo listing methods, but this specific method isn't listed.
1565
+ // This implies a mismatch for strict signature check based on TypeInfo.
1566
+ return false
1567
+ }
1568
+ }
1569
+ }
1308
1570
 
1309
- const hasAnyMethod = Array.from(normalizedType.methods).some(
1310
- (method) => typeof value[method] === 'function',
1311
- )
1571
+ // Fallback: Original behavior if value has no TypeInfo that lists methods,
1572
+ // or if the method wasn't found in its TypeInfo (covered by 'else' returning false above).
1573
+ // The original comment was: "For now, presence and function type is checked by matchesStructType/matchesInterfaceType"
1574
+ // This 'return true' implies that if we couldn't do a full signature check via TypeInfo,
1575
+ // we still consider it a match if the function simply exists on the object.
1576
+ return true;
1577
+ });
1312
1578
 
1313
- if (allMethodsMatch && hasAnyMethod && normalizedType.methods.size > 0) {
1314
- // For interface-to-concrete type assertions, we just need to check methods
1315
- return { value: value as T, ok: true }
1579
+ if (allMethodsMatch) {
1580
+ return { value: value as T, ok: true };
1316
1581
  }
1317
1582
  }
1318
1583