skittles 1.2.4 → 1.2.6

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.
@@ -1,17 +1,19 @@
1
1
  import ts from "typescript";
2
- import type { SkittlesContract, SkittlesFunction, SkittlesParameter, SkittlesType, Statement, Expression } from "../types/index.ts";
2
+ import type { SkittlesContract, SkittlesFunction, SkittlesParameter, SkittlesType, SkittlesContractInterface, Statement, Expression } from "../types/index.ts";
3
3
  /**
4
- * Pre-scan a source file to collect interfaces (structs) and enums
5
- * without parsing any classes. Used by the compiler to resolve
6
- * cross-file type references.
4
+ * Pre-scan a source file to collect type aliases (structs), interfaces
5
+ * (contract interfaces), and enums without parsing any classes.
6
+ * Used by the compiler to resolve cross-file type references.
7
7
  */
8
8
  export declare function collectTypes(source: string, filePath: string): {
9
9
  structs: Map<string, SkittlesParameter[]>;
10
10
  enums: Map<string, string[]>;
11
+ contractInterfaces: Map<string, SkittlesContractInterface>;
11
12
  };
12
13
  export interface ExternalTypes {
13
14
  structs?: Map<string, SkittlesParameter[]>;
14
15
  enums?: Map<string, string[]>;
16
+ contractInterfaces?: Map<string, SkittlesContractInterface>;
15
17
  }
16
18
  export interface ExternalFunctions {
17
19
  functions?: SkittlesFunction[];
@@ -29,6 +31,6 @@ export declare function collectFunctions(source: string, filePath: string): {
29
31
  export declare function parseType(node: ts.TypeNode): SkittlesType;
30
32
  export declare function parseExpression(node: ts.Expression): Expression;
31
33
  export declare function parseStatement(node: ts.Statement, varTypes: Map<string, SkittlesType>, eventNames?: Set<string>): Statement;
32
- export declare function inferStateMutability(body: Statement[]): "pure" | "view" | "nonpayable" | "payable";
34
+ export declare function inferStateMutability(body: Statement[], varTypes?: Map<string, SkittlesType>): "pure" | "view" | "nonpayable" | "payable";
33
35
  export declare function inferType(expr: Expression, varTypes: Map<string, SkittlesType>): SkittlesType | undefined;
34
36
  //# sourceMappingURL=parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/compiler/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,KAAK,EACV,gBAAgB,EAEhB,gBAAgB,EAGhB,iBAAiB,EACjB,YAAY,EAGZ,SAAS,EACT,UAAU,EAGX,MAAM,mBAAmB,CAAC;AAY3B;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAC9D,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC9B,CAkCA;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACrC;AAED,wBAAgB,KAAK,CACnB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,aAAa,CAAC,EAAE,aAAa,EAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,gBAAgB,EAAE,CAkGpB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAClE,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC,CAyCA;AAyiBD,wBAAgB,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,GAAG,YAAY,CA0DzD;AAMD,wBAAgB,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,UAAU,CAqM/D;AAMD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,EAAE,CAAC,SAAS,EAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACnC,UAAU,GAAE,GAAG,CAAC,MAAM,CAAa,GAClC,SAAS,CAmOX;AAqPD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,SAAS,CAgDlG;AAMD,wBAAgB,SAAS,CACvB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAClC,YAAY,GAAG,SAAS,CA+D1B"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/compiler/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,KAAK,EACV,gBAAgB,EAEhB,gBAAgB,EAGhB,iBAAiB,EACjB,YAAY,EAEZ,yBAAyB,EAGzB,SAAS,EACT,UAAU,EAGX,MAAM,mBAAmB,CAAC;AAa3B;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAC9D,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC5D,CA2CA;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9B,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACrC;AAED,wBAAgB,KAAK,CACnB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,aAAa,CAAC,EAAE,aAAa,EAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,gBAAgB,EAAE,CAmHpB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAClE,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC,CA4CA;AAspBD,wBAAgB,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,GAAG,YAAY,CAiEzD;AAMD,wBAAgB,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,UAAU,CAqM/D;AAMD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,EAAE,CAAC,SAAS,EAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACnC,UAAU,GAAE,GAAG,CAAC,MAAM,CAAa,GAClC,SAAS,CAmOX;AAqPD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,SAAS,CAmDxI;AAMD,wBAAgB,SAAS,CACvB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAClC,YAAY,GAAG,SAAS,CA+D1B"}
@@ -1,6 +1,7 @@
1
1
  import ts from "typescript";
2
2
  // Module-level registries, populated during parse()
3
3
  let _knownStructs = new Map();
4
+ let _knownContractInterfaces = new Set();
4
5
  let _knownEnums = new Set();
5
6
  let _knownCustomErrors = new Set();
6
7
  let _fileConstants = new Map();
@@ -8,24 +9,32 @@ let _fileConstants = new Map();
8
9
  // Main entry
9
10
  // ============================================================
10
11
  /**
11
- * Pre-scan a source file to collect interfaces (structs) and enums
12
- * without parsing any classes. Used by the compiler to resolve
13
- * cross-file type references.
12
+ * Pre-scan a source file to collect type aliases (structs), interfaces
13
+ * (contract interfaces), and enums without parsing any classes.
14
+ * Used by the compiler to resolve cross-file type references.
14
15
  */
15
16
  export function collectTypes(source, filePath) {
16
17
  const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);
17
18
  const structs = new Map();
18
19
  const enums = new Map();
20
+ const contractInterfaces = new Map();
19
21
  // Temporarily set module registries so parseType can resolve references
20
22
  const prevStructs = _knownStructs;
21
23
  const prevEnums = _knownEnums;
24
+ const prevInterfaces = _knownContractInterfaces;
22
25
  _knownStructs = structs;
23
26
  _knownEnums = new Set();
27
+ _knownContractInterfaces = new Set();
24
28
  ts.forEachChild(sourceFile, (node) => {
25
- if (ts.isInterfaceDeclaration(node) && node.name) {
26
- const fields = parseInterfaceFields(node);
29
+ if (ts.isTypeAliasDeclaration(node) && node.name && ts.isTypeLiteralNode(node.type)) {
30
+ const fields = parseTypeLiteralFields(node.type);
27
31
  structs.set(node.name.text, fields);
28
32
  }
33
+ if (ts.isInterfaceDeclaration(node) && node.name) {
34
+ const iface = parseInterfaceAsContractInterface(node);
35
+ contractInterfaces.set(node.name.text, iface);
36
+ _knownContractInterfaces.add(node.name.text);
37
+ }
29
38
  if (ts.isEnumDeclaration(node) && node.name) {
30
39
  const members = node.members.map((m) => ts.isIdentifier(m.name) ? m.name.text : "Unknown");
31
40
  enums.set(node.name.text, members);
@@ -33,12 +42,14 @@ export function collectTypes(source, filePath) {
33
42
  });
34
43
  _knownStructs = prevStructs;
35
44
  _knownEnums = prevEnums;
36
- return { structs, enums };
45
+ _knownContractInterfaces = prevInterfaces;
46
+ return { structs, enums, contractInterfaces };
37
47
  }
38
48
  export function parse(source, filePath, externalTypes, externalFunctions) {
39
49
  const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);
40
50
  const structs = new Map();
41
51
  const enums = new Map();
52
+ const contractInterfaces = new Map();
42
53
  const contracts = [];
43
54
  // Seed with externally resolved types (from other files)
44
55
  if (externalTypes?.structs) {
@@ -51,9 +62,15 @@ export function parse(source, filePath, externalTypes, externalFunctions) {
51
62
  enums.set(name, members);
52
63
  }
53
64
  }
65
+ if (externalTypes?.contractInterfaces) {
66
+ for (const [name, iface] of externalTypes.contractInterfaces) {
67
+ contractInterfaces.set(name, iface);
68
+ }
69
+ }
70
+ // First pass: collect structs and enums so they are available when parsing interfaces
54
71
  ts.forEachChild(sourceFile, (node) => {
55
- if (ts.isInterfaceDeclaration(node) && node.name) {
56
- const fields = parseInterfaceFields(node);
72
+ if (ts.isTypeAliasDeclaration(node) && node.name && ts.isTypeLiteralNode(node.type)) {
73
+ const fields = parseTypeLiteralFields(node.type);
57
74
  structs.set(node.name.text, fields);
58
75
  }
59
76
  if (ts.isEnumDeclaration(node) && node.name) {
@@ -61,9 +78,17 @@ export function parse(source, filePath, externalTypes, externalFunctions) {
61
78
  enums.set(node.name.text, members);
62
79
  }
63
80
  });
64
- const customErrors = new Map();
65
81
  _knownStructs = structs;
66
82
  _knownEnums = new Set(enums.keys());
83
+ // Second pass: parse interfaces (may reference struct/enum types collected above)
84
+ ts.forEachChild(sourceFile, (node) => {
85
+ if (ts.isInterfaceDeclaration(node) && node.name) {
86
+ const iface = parseInterfaceAsContractInterface(node);
87
+ contractInterfaces.set(node.name.text, iface);
88
+ }
89
+ });
90
+ const customErrors = new Map();
91
+ _knownContractInterfaces = new Set(contractInterfaces.keys());
67
92
  _knownCustomErrors = new Set();
68
93
  _fileConstants = new Map();
69
94
  // Collect file level constants (const declarations outside classes)
@@ -110,7 +135,7 @@ export function parse(source, filePath, externalTypes, externalFunctions) {
110
135
  _knownCustomErrors.add(node.name.text);
111
136
  }
112
137
  else {
113
- contracts.push(parseClass(node, filePath, structs, enums, customErrors, fileFunctions, fileConstants));
138
+ contracts.push(parseClass(node, filePath, structs, enums, contractInterfaces, customErrors, fileFunctions, fileConstants));
114
139
  }
115
140
  }
116
141
  });
@@ -126,11 +151,13 @@ export function collectFunctions(source, filePath) {
126
151
  const constants = new Map();
127
152
  const emptyVarTypes = new Map();
128
153
  const emptyEventNames = new Set();
129
- // Need to set up struct/enum registries for type parsing
154
+ // Need to set up struct/enum/interface registries for type parsing
130
155
  const prevStructs = _knownStructs;
131
156
  const prevEnums = _knownEnums;
157
+ const prevInterfaces = _knownContractInterfaces;
132
158
  _knownStructs = new Map();
133
159
  _knownEnums = new Set();
160
+ _knownContractInterfaces = new Set();
134
161
  ts.forEachChild(sourceFile, (node) => {
135
162
  if (ts.isFunctionDeclaration(node) && node.name && node.body) {
136
163
  functions.push(parseStandaloneFunction(node, emptyVarTypes, emptyEventNames));
@@ -150,6 +177,7 @@ export function collectFunctions(source, filePath) {
150
177
  });
151
178
  _knownStructs = prevStructs;
152
179
  _knownEnums = prevEnums;
180
+ _knownContractInterfaces = prevInterfaces;
153
181
  return { functions, constants };
154
182
  }
155
183
  function parseArrayDestructuring(pattern, initializer, varTypes) {
@@ -262,7 +290,7 @@ function parseErrorClass(node) {
262
290
  }
263
291
  return [];
264
292
  }
265
- function parseInterfaceFields(node) {
293
+ function parseTypeLiteralFields(node) {
266
294
  const fields = [];
267
295
  for (const member of node.members) {
268
296
  if (ts.isPropertySignature(member) &&
@@ -277,10 +305,30 @@ function parseInterfaceFields(node) {
277
305
  }
278
306
  return fields;
279
307
  }
308
+ function parseInterfaceAsContractInterface(node) {
309
+ const name = node.name.text;
310
+ const functions = [];
311
+ for (const member of node.members) {
312
+ if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
313
+ const propName = member.name.text;
314
+ const returnType = member.type
315
+ ? parseType(member.type)
316
+ : { kind: "uint256" };
317
+ functions.push({ name: propName, parameters: [], returnType, stateMutability: "view" });
318
+ }
319
+ if (ts.isMethodSignature(member) && member.name && ts.isIdentifier(member.name)) {
320
+ const methodName = member.name.text;
321
+ const parameters = member.parameters.map(parseParameter);
322
+ const returnType = member.type ? parseType(member.type) : null;
323
+ functions.push({ name: methodName, parameters, returnType });
324
+ }
325
+ }
326
+ return { name, functions };
327
+ }
280
328
  // ============================================================
281
329
  // Class level parsing
282
330
  // ============================================================
283
- function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new Map(), knownCustomErrors = new Map(), fileFunctions = [], fileConstants = new Map()) {
331
+ function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new Map(), knownContractInterfaces = new Map(), knownCustomErrors = new Map(), fileFunctions = [], fileConstants = new Map()) {
284
332
  const name = node.name?.text ?? "Unknown";
285
333
  const variables = [];
286
334
  const functions = [];
@@ -296,9 +344,13 @@ function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new M
296
344
  }
297
345
  }
298
346
  }
299
- // `implements` is accepted but not added to inherits.
300
- // TypeScript enforces the shape constraint at compile time.
301
- // Solidity doesn't have a separate `implements` keyword.
347
+ if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
348
+ for (const type of clause.types) {
349
+ if (ts.isIdentifier(type.expression)) {
350
+ inherits.push(type.expression.text);
351
+ }
352
+ }
353
+ }
302
354
  }
303
355
  }
304
356
  // Collect arrow function properties separately so they can be parsed as methods
@@ -371,6 +423,19 @@ function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new M
371
423
  // Third pass: propagate state mutability through call chains.
372
424
  // If function A calls this.B(), and B is nonpayable, then A is also nonpayable.
373
425
  propagateStateMutability(functions);
426
+ // Build set of interface names this class implements
427
+ const implementedInterfaceNames = new Set();
428
+ if (node.heritageClauses) {
429
+ for (const clause of node.heritageClauses) {
430
+ if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
431
+ for (const type of clause.types) {
432
+ if (ts.isIdentifier(type.expression) && knownContractInterfaces.has(type.expression.text)) {
433
+ implementedInterfaceNames.add(type.expression.text);
434
+ }
435
+ }
436
+ }
437
+ }
438
+ }
374
439
  const contractStructs = [];
375
440
  for (const [sName, fields] of knownStructs) {
376
441
  contractStructs.push({ name: sName, fields });
@@ -379,6 +444,72 @@ function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new M
379
444
  for (const [eName, members] of knownEnums) {
380
445
  contractEnums.push({ name: eName, members });
381
446
  }
447
+ // Determine which interfaces this contract actually references
448
+ const usedIfaceNames = new Set();
449
+ for (const iName of inherits) {
450
+ if (knownContractInterfaces.has(iName))
451
+ usedIfaceNames.add(iName);
452
+ }
453
+ for (const v of variables) {
454
+ collectContractInterfaceTypeRefs(v.type, usedIfaceNames);
455
+ }
456
+ for (const f of functions) {
457
+ for (const p of f.parameters)
458
+ collectContractInterfaceTypeRefs(p.type, usedIfaceNames);
459
+ if (f.returnType)
460
+ collectContractInterfaceTypeRefs(f.returnType, usedIfaceNames);
461
+ }
462
+ if (ctor) {
463
+ for (const p of ctor.parameters)
464
+ collectContractInterfaceTypeRefs(p.type, usedIfaceNames);
465
+ }
466
+ // Deep copy only used interfaces so mutability updates don't leak to shared state
467
+ const contractIfaceList = [];
468
+ for (const ifName of usedIfaceNames) {
469
+ const iface = knownContractInterfaces.get(ifName);
470
+ if (iface) {
471
+ contractIfaceList.push({
472
+ name: iface.name,
473
+ functions: iface.functions.map((fn) => ({ ...fn })),
474
+ });
475
+ }
476
+ }
477
+ // Fourth pass: for implemented interfaces, derive method mutabilities
478
+ // from the actual implementation and mark implementing members as override.
479
+ if (implementedInterfaceNames.size > 0) {
480
+ const interfaceFnNames = new Set();
481
+ for (const ifaceName of implementedInterfaceNames) {
482
+ const iface = contractIfaceList.find((i) => i.name === ifaceName);
483
+ if (!iface)
484
+ continue;
485
+ for (const ifn of iface.functions) {
486
+ interfaceFnNames.add(ifn.name);
487
+ if (ifn.stateMutability)
488
+ continue;
489
+ const impl = functions.find((f) => f.name === ifn.name);
490
+ if (impl) {
491
+ ifn.stateMutability = impl.stateMutability;
492
+ }
493
+ else {
494
+ const varImpl = variables.find((v) => v.name === ifn.name && v.visibility === "public");
495
+ if (varImpl) {
496
+ ifn.stateMutability = "view";
497
+ }
498
+ }
499
+ }
500
+ }
501
+ for (const fn of functions) {
502
+ if (interfaceFnNames.has(fn.name)) {
503
+ fn.isOverride = true;
504
+ fn.isVirtual = false;
505
+ }
506
+ }
507
+ for (const v of variables) {
508
+ if (v.visibility === "public" && interfaceFnNames.has(v.name) && !v.constant && !v.immutable) {
509
+ v.isOverride = true;
510
+ }
511
+ }
512
+ }
382
513
  const contractCustomErrors = [];
383
514
  for (const [cName, params] of knownCustomErrors) {
384
515
  contractCustomErrors.push({ name: cName, parameters: params });
@@ -395,6 +526,7 @@ function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new M
395
526
  events,
396
527
  structs: contractStructs,
397
528
  enums: contractEnums,
529
+ contractInterfaces: contractIfaceList,
398
530
  customErrors: contractCustomErrors,
399
531
  ctor,
400
532
  inherits,
@@ -507,7 +639,7 @@ function parseMethod(node, varTypes, eventNames) {
507
639
  : null;
508
640
  const visibility = getVisibility(node.modifiers);
509
641
  const body = node.body ? parseBlock(node.body, varTypes, eventNames) : [];
510
- const stateMutability = inferStateMutability(body);
642
+ const stateMutability = inferStateMutability(body, varTypes);
511
643
  const isOverride = hasModifier(node.modifiers, ts.SyntaxKind.OverrideKeyword);
512
644
  const isVirtual = !isOverride;
513
645
  return { name, parameters, returnType, visibility, stateMutability, isVirtual, isOverride, body };
@@ -520,7 +652,7 @@ function parseGetAccessor(node, varTypes, eventNames) {
520
652
  : null;
521
653
  const visibility = getVisibility(node.modifiers);
522
654
  const body = node.body ? parseBlock(node.body, varTypes, eventNames) : [];
523
- const stateMutability = inferStateMutability(body);
655
+ const stateMutability = inferStateMutability(body, varTypes);
524
656
  const isOverride = hasModifier(node.modifiers, ts.SyntaxKind.OverrideKeyword);
525
657
  const isVirtual = !isOverride;
526
658
  return { name, parameters, returnType, visibility, stateMutability, isVirtual, isOverride, body };
@@ -531,7 +663,7 @@ function parseSetAccessor(node, varTypes, eventNames) {
531
663
  const returnType = null; // setters don't return
532
664
  const visibility = getVisibility(node.modifiers);
533
665
  const body = node.body ? parseBlock(node.body, varTypes, eventNames) : [];
534
- const stateMutability = inferStateMutability(body);
666
+ const stateMutability = inferStateMutability(body, varTypes);
535
667
  const isOverride = hasModifier(node.modifiers, ts.SyntaxKind.OverrideKeyword);
536
668
  const isVirtual = !isOverride;
537
669
  return { name, parameters, returnType, visibility, stateMutability, isVirtual, isOverride, body };
@@ -554,7 +686,7 @@ function parseArrowProperty(node, varTypes, eventNames) {
554
686
  body = [{ kind: "return", value: parseExpression(arrow.body) }];
555
687
  }
556
688
  }
557
- const stateMutability = inferStateMutability(body);
689
+ const stateMutability = inferStateMutability(body, varTypes);
558
690
  const isOverride = hasModifier(node.modifiers, ts.SyntaxKind.OverrideKeyword);
559
691
  const isVirtual = !isOverride;
560
692
  return { name, parameters, returnType, visibility, stateMutability, isVirtual, isOverride, body };
@@ -599,13 +731,19 @@ export function parseType(node) {
599
731
  structFields: _knownStructs.get(name),
600
732
  };
601
733
  }
734
+ if (_knownContractInterfaces.has(name)) {
735
+ return {
736
+ kind: "contract-interface",
737
+ structName: name,
738
+ };
739
+ }
602
740
  if (_knownEnums.has(name)) {
603
741
  return {
604
742
  kind: "enum",
605
743
  structName: name,
606
744
  };
607
745
  }
608
- throw new Error(`Unsupported type reference: "${name}". Skittles supports number, string, boolean, address, bytes, Record<K,V>, T[], interface structs, and enums.`);
746
+ throw new Error(`Unsupported type reference: "${name}". Skittles supports number, string, boolean, address, bytes, Record<K,V>, T[], type structs, interfaces, and enums.`);
609
747
  }
610
748
  if (ts.isArrayTypeNode(node)) {
611
749
  return {
@@ -1222,7 +1360,7 @@ function collectThisCalls(stmts) {
1222
1360
  });
1223
1361
  return names;
1224
1362
  }
1225
- export function inferStateMutability(body) {
1363
+ export function inferStateMutability(body, varTypes) {
1226
1364
  let readsState = false;
1227
1365
  let writesState = false;
1228
1366
  let usesMsgValue = false;
@@ -1248,6 +1386,9 @@ export function inferStateMutability(body) {
1248
1386
  if (expr.kind === "call" && isStateMutatingCall(expr)) {
1249
1387
  writesState = true;
1250
1388
  }
1389
+ if (expr.kind === "call" && varTypes && isExternalContractCall(expr, varTypes)) {
1390
+ writesState = true;
1391
+ }
1251
1392
  }, (stmt) => {
1252
1393
  if (stmt.kind === "emit") {
1253
1394
  writesState = true;
@@ -1330,6 +1471,15 @@ export function inferType(expr, varTypes) {
1330
1471
  // ============================================================
1331
1472
  // Helpers
1332
1473
  // ============================================================
1474
+ function collectContractInterfaceTypeRefs(type, refs) {
1475
+ if (type.kind === "contract-interface" && type.structName) {
1476
+ refs.add(type.structName);
1477
+ }
1478
+ if (type.keyType)
1479
+ collectContractInterfaceTypeRefs(type.keyType, refs);
1480
+ if (type.valueType)
1481
+ collectContractInterfaceTypeRefs(type.valueType, refs);
1482
+ }
1333
1483
  function isStateAccess(expr) {
1334
1484
  if (expr.kind === "property-access" &&
1335
1485
  expr.object.kind === "identifier" &&
@@ -1341,6 +1491,19 @@ function isStateAccess(expr) {
1341
1491
  }
1342
1492
  return false;
1343
1493
  }
1494
+ function isExternalContractCall(expr, varTypes) {
1495
+ if (expr.callee.kind === "property-access" &&
1496
+ expr.callee.object.kind === "property-access" &&
1497
+ expr.callee.object.object.kind === "identifier" &&
1498
+ expr.callee.object.object.name === "this") {
1499
+ const propName = expr.callee.object.property;
1500
+ const propType = varTypes.get(propName);
1501
+ if (propType && propType.kind === "contract-interface") {
1502
+ return true;
1503
+ }
1504
+ }
1505
+ return false;
1506
+ }
1344
1507
  function isStateMutatingCall(expr) {
1345
1508
  if (expr.callee.kind !== "property-access")
1346
1509
  return false;