@harmoniclabs/pebble 0.1.8 → 0.1.9

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.
Files changed (42) hide show
  1. package/dist/ast/nodes/expr/IsExpr.d.ts +10 -1
  2. package/dist/ast/nodes/expr/IsExpr.js +9 -13
  3. package/dist/ast/nodes/expr/PebbleExpr.d.ts +2 -1
  4. package/dist/ast/nodes/expr/PebbleExpr.js +2 -1
  5. package/dist/ast/nodes/statements/declarations/ContractDecl.d.ts +3 -1
  6. package/dist/ast/nodes/statements/declarations/ContractDecl.js +3 -1
  7. package/dist/ast/nodes/statements/declarations/StateDecl.d.ts +12 -0
  8. package/dist/ast/nodes/statements/declarations/StateDecl.js +12 -0
  9. package/dist/compiler/AstCompiler/internal/_deriveContractBody/_deriveContractBody.js +155 -6
  10. package/dist/compiler/AstCompiler/internal/exprs/_compileCaseExpr.d.ts +1 -1
  11. package/dist/compiler/AstCompiler/internal/exprs/_compileCaseExpr.js +30 -4
  12. package/dist/compiler/AstCompiler/internal/exprs/_compileExpr.js +4 -1
  13. package/dist/compiler/AstCompiler/internal/exprs/_compileIsExpr.d.ts +5 -1
  14. package/dist/compiler/AstCompiler/internal/exprs/_compileIsExpr.js +20 -41
  15. package/dist/compiler/AstCompiler/internal/exprs/_compileTernaryExpr.js +7 -2
  16. package/dist/compiler/AstCompiler/internal/statements/_compileAssertStmt.js +4 -0
  17. package/dist/compiler/AstCompiler/internal/statements/_compileIfStmt.js +7 -2
  18. package/dist/compiler/AstCompiler/internal/statements/_compileMatchStmt.d.ts +1 -1
  19. package/dist/compiler/AstCompiler/internal/statements/_compileMatchStmt.js +17 -4
  20. package/dist/compiler/AstCompiler/scope/AstScope.d.ts +12 -0
  21. package/dist/compiler/AstCompiler/scope/AstScope.js +25 -2
  22. package/dist/compiler/AstCompiler/utils/extractIsNarrowings.d.ts +30 -0
  23. package/dist/compiler/AstCompiler/utils/extractIsNarrowings.js +110 -0
  24. package/dist/compiler/Compiler.js +7 -3
  25. package/dist/compiler/TirCompiler/expressify/expressifyVars.js +6 -0
  26. package/dist/compiler/tir/expressions/TirCallExpr.js +4 -0
  27. package/dist/compiler/tir/expressions/TirCaseExpr.js +38 -7
  28. package/dist/compiler/tir/expressions/TirExpr.d.ts +2 -1
  29. package/dist/compiler/tir/expressions/TirExpr.js +2 -1
  30. package/dist/compiler/tir/expressions/TirFuncExpr.js +4 -1
  31. package/dist/compiler/tir/expressions/TirIsExpr.d.ts +23 -0
  32. package/dist/compiler/tir/expressions/TirIsExpr.js +87 -0
  33. package/dist/compiler/tir/types/TirStructType.d.ts +33 -2
  34. package/dist/compiler/tir/types/TirStructType.js +69 -4
  35. package/dist/compiler/tir/types/utils/canAssignTo.js +14 -0
  36. package/dist/parser/Parser.d.ts +2 -0
  37. package/dist/parser/Parser.js +87 -22
  38. package/dist/tokenizer/Token.d.ts +79 -78
  39. package/dist/tokenizer/Token.js +79 -78
  40. package/dist/tokenizer/utils/tokenFromKeyword.js +7 -2
  41. package/dist/tokenizer/utils/tokenIsAlsoIdentifier.js +1 -0
  42. package/package.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { Identifier } from "../../../../ast/nodes/common/Identifier.js";
2
+ import { ParentesizedExpr } from "../../../../ast/nodes/expr/ParentesizedExpr.js";
1
3
  import { ArrayLikeDeconstr } from "../../../../ast/nodes/statements/declarations/VarDecl/ArrayLikeDeconstr.js";
2
4
  import { NamedDeconstructVarDecl } from "../../../../ast/nodes/statements/declarations/VarDecl/NamedDeconstructVarDecl.js";
3
5
  import { SimpleVarDecl } from "../../../../ast/nodes/statements/declarations/VarDecl/SimpleVarDecl.js";
@@ -14,6 +16,11 @@ import { wrapManyStatements } from "../../utils/wrapManyStatementsOrReturnSame.j
14
16
  import { _compileExpr } from "../exprs/_compileExpr.js";
15
17
  import { _compileStatement } from "./_compileStatement.js";
16
18
  import { _compileNamedDeconstructVarDecl, _compileSingleDeconstructVarDecl } from "./_compileVarStmt.js";
19
+ function unwrapToIdentifierName(expr) {
20
+ while (expr instanceof ParentesizedExpr)
21
+ expr = expr.expr;
22
+ return expr instanceof Identifier ? expr.text : undefined;
23
+ }
17
24
  export function _compileMatchStmt(ctx, stmt) {
18
25
  if (!ctx.functionCtx)
19
26
  return ctx.error(DiagnosticCode.A_match_statement_can_only_be_used_within_a_function_body, stmt.range);
@@ -33,10 +40,11 @@ export function _compileMatchStmt(ctx, stmt) {
33
40
  const missingCtors = ctorNames.slice();
34
41
  if (stmt.cases.length === 0)
35
42
  return ctx.error(DiagnosticCode.A_match_statement_must_have_at_least_one_case, stmt.range);
43
+ const matchedVarName = unwrapToIdentifierName(stmt.matchExpr);
36
44
  const cases = [];
37
45
  const constrNamesAlreadySpecified = [];
38
46
  for (const matchCase of stmt.cases) {
39
- const branch = _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, constrNamesAlreadySpecified);
47
+ const branch = _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, constrNamesAlreadySpecified, matchedVarName);
40
48
  if (!branch)
41
49
  return undefined;
42
50
  /*
@@ -67,7 +75,7 @@ export function _compileMatchStmt(ctx, stmt) {
67
75
  }
68
76
  return [new TirMatchStmt(matchExpr, cases, wildcardCase, stmt.range)];
69
77
  }
70
- export function _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, constrNamesAlreadySpecified) {
78
+ export function _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, constrNamesAlreadySpecified, matchedVarName) {
71
79
  /*
72
80
  const pattern = _compileVarDecl( ctx, matchCase.pattern, deconstructableType );
73
81
  if( !pattern ) return undefined;
@@ -101,10 +109,15 @@ export function _compileTirMatchStmtCase(ctx, matchCase, deconstructableType, co
101
109
  constrNamesAlreadySpecified.push(deconstructedCtorName);
102
110
  if (deconstructableType instanceof TirSoPStructType
103
111
  || deconstructableType instanceof TirDataStructType) {
104
- const ctorDef = deconstructableType.constructors.find(c => c.name === deconstructedCtorName);
105
- if (!ctorDef)
112
+ const localIdx = deconstructableType.constructors.findIndex(c => c.name === deconstructedCtorName);
113
+ if (localIdx < 0)
106
114
  return ctx.error(DiagnosticCode.Unknown_0_constructor_1, pattern.name.range, deconstructableType.toString(), deconstructedCtorName);
107
115
  const branchCtx = ctx.newBranchChildScope();
116
+ // narrow the matched variable (if it's a plain identifier) to the matched constructor
117
+ if (matchedVarName) {
118
+ const parentIdx = deconstructableType.parentCtorIdx(localIdx);
119
+ branchCtx.scope.narrowVariable(matchedVarName, deconstructableType.narrowTo([parentIdx]));
120
+ }
108
121
  const branchArg = _compileNamedDeconstructVarDecl(branchCtx, pattern, deconstructableType);
109
122
  if (!branchArg)
110
123
  return undefined;
@@ -74,12 +74,24 @@ export declare class AstScope {
74
74
  * ast name -> variable infos (name, type, isConstant)
75
75
  */
76
76
  readonly variables: Map<string, IVariableInfos>;
77
+ /**
78
+ * Flow-sensitive type narrowings active in this scope.
79
+ * Variable name -> narrowed type (overrides the type recorded in `variables`
80
+ * or any parent scope's narrowing/binding when resolving this variable).
81
+ */
82
+ readonly narrowings: Map<string, TirType>;
77
83
  readonly aviableConstructors: Map<string, IAvaiableConstructor>;
78
84
  private _isReadonly;
79
85
  readonly infos: ScopeInfos;
80
86
  constructor(parent: AstScope | undefined, program: TypedProgram, infos: Partial<ScopeInfos>);
81
87
  defineValue(valueInfos: IVariableInfos): boolean;
82
88
  resolveValue(name: string): ResolveValueResult | undefined;
89
+ /**
90
+ * Records a flow-sensitive narrowing for `name` in THIS scope.
91
+ * Subsequent `resolveValue(name)` calls (in this scope or its
92
+ * descendants, until shadowed) will return the narrowed type.
93
+ */
94
+ narrowVariable(name: string, narrowedType: TirType): void;
83
95
  allVariables(): string[];
84
96
  defineUnambigousType(name: string, tirTypeKey: string, allowsDataEncoding: boolean, methodsNames: Map<AstFuncName, TirFuncName>): boolean;
85
97
  defineType(name: string, possibleTirTypes: PossibleTirTypes): boolean;
@@ -52,6 +52,12 @@ export class AstScope {
52
52
  * ast name -> variable infos (name, type, isConstant)
53
53
  */
54
54
  variables = new Map();
55
+ /**
56
+ * Flow-sensitive type narrowings active in this scope.
57
+ * Variable name -> narrowed type (overrides the type recorded in `variables`
58
+ * or any parent scope's narrowing/binding when resolving this variable).
59
+ */
60
+ narrowings = new Map();
55
61
  aviableConstructors = new Map();
56
62
  _isReadonly = false;
57
63
  infos;
@@ -75,10 +81,13 @@ export class AstScope {
75
81
  return true;
76
82
  }
77
83
  resolveValue(name) {
84
+ const narrowed = this.narrowings.get(name);
78
85
  const localValue = this.variables.get(name);
79
86
  if (localValue)
80
87
  return {
81
- variableInfos: localValue,
88
+ variableInfos: narrowed
89
+ ? { ...localValue, type: narrowed }
90
+ : localValue,
82
91
  isDefinedOutsideFuncScope: false
83
92
  };
84
93
  if (this.parent) {
@@ -86,12 +95,24 @@ export class AstScope {
86
95
  if (!parentValue)
87
96
  return undefined;
88
97
  return {
89
- variableInfos: parentValue.variableInfos,
98
+ variableInfos: narrowed
99
+ ? { ...parentValue.variableInfos, type: narrowed }
100
+ : parentValue.variableInfos,
90
101
  isDefinedOutsideFuncScope: parentValue.isDefinedOutsideFuncScope || this.infos.isFunctionDeclScope
91
102
  };
92
103
  }
93
104
  return undefined;
94
105
  }
106
+ /**
107
+ * Records a flow-sensitive narrowing for `name` in THIS scope.
108
+ * Subsequent `resolveValue(name)` calls (in this scope or its
109
+ * descendants, until shadowed) will return the narrowed type.
110
+ */
111
+ narrowVariable(name, narrowedType) {
112
+ if (this._isReadonly)
113
+ return;
114
+ this.narrowings.set(name, narrowedType);
115
+ }
95
116
  allVariables() {
96
117
  return (this.parent?.allVariables() ?? []).concat(Array.from(this.variables.keys()));
97
118
  }
@@ -185,6 +206,8 @@ export class AstScope {
185
206
  const cloned = new AstScope(this.parent, this.program, this.infos);
186
207
  for (const [key, value] of this.variables)
187
208
  cloned.variables.set(key, { ...value });
209
+ for (const [key, value] of this.narrowings)
210
+ cloned.narrowings.set(key, value.clone());
188
211
  for (const [key, value] of this.functions)
189
212
  cloned.functions.set(key, value);
190
213
  for (const [key, value] of this.types)
@@ -0,0 +1,30 @@
1
+ import { TirExpr } from "../../tir/expressions/TirExpr.js";
2
+ import { TirStructType } from "../../tir/types/TirStructType.js";
3
+ export interface IsNarrowing {
4
+ varName: string;
5
+ /** indexes (in the parent struct's `constructors` array) still possible after the check */
6
+ parentCtorIdxs: number[];
7
+ /** original variable type (before narrowing); the parent struct, used to build the narrowed clone */
8
+ parentStructType: TirStructType;
9
+ }
10
+ /**
11
+ * Walk a boolean expression and collect flow-sensitive narrowings that
12
+ * can be assumed true when the expression evaluates to `polarity`.
13
+ *
14
+ * Rules:
15
+ * - `x is C` ⇒ on TRUE: x narrows to {C}; on FALSE: x narrows to "all but C"
16
+ * - `!cond` ⇒ flip polarity and recurse
17
+ * - `a && b` ⇒ on TRUE: union of narrowings from a and b; on FALSE: nothing
18
+ * - `a || b` ⇒ on TRUE: nothing; on FALSE: union of FALSE-narrowings from a and b
19
+ * - parens are transparent
20
+ *
21
+ * Multiple narrowings on the same variable intersect (most restrictive wins).
22
+ */
23
+ export declare function extractIsNarrowings(expr: TirExpr, polarity: boolean): IsNarrowing[];
24
+ /**
25
+ * Apply a list of narrowings to the current scope of `ctx`.
26
+ * Builds a narrowed clone of the parent struct and registers it
27
+ * via `scope.narrowVariable`.
28
+ */
29
+ import type { AstScope } from "../scope/AstScope.js";
30
+ export declare function applyNarrowingsToScope(scope: AstScope, narrowings: IsNarrowing[]): void;
@@ -0,0 +1,110 @@
1
+ import { TirIsExpr } from "../../tir/expressions/TirIsExpr.js";
2
+ import { TirParentesizedExpr } from "../../tir/expressions/TirParentesizedExpr.js";
3
+ import { TirUnaryExclamation } from "../../tir/expressions/unary/TirUnaryExclamation.js";
4
+ import { TirVariableAccessExpr } from "../../tir/expressions/TirVariableAccessExpr.js";
5
+ // NOTE: TirLogicalAndExpr / TirLogicalOrExpr come from TirBinaryExpr.ts which
6
+ // pulls in the IR -> UPLC compiler. Importing them here at the top would
7
+ // create a require-cycle (this file is loaded by `_compileAssertStmt.ts`
8
+ // during ast-compilation, before `compileIRToUPLC` finishes initializing).
9
+ // We detect them by constructor name instead.
10
+ import { TirDataStructType, TirSoPStructType } from "../../tir/types/TirStructType.js";
11
+ import { getStructType } from "../../tir/types/utils/canAssignTo.js";
12
+ /**
13
+ * Walk a boolean expression and collect flow-sensitive narrowings that
14
+ * can be assumed true when the expression evaluates to `polarity`.
15
+ *
16
+ * Rules:
17
+ * - `x is C` ⇒ on TRUE: x narrows to {C}; on FALSE: x narrows to "all but C"
18
+ * - `!cond` ⇒ flip polarity and recurse
19
+ * - `a && b` ⇒ on TRUE: union of narrowings from a and b; on FALSE: nothing
20
+ * - `a || b` ⇒ on TRUE: nothing; on FALSE: union of FALSE-narrowings from a and b
21
+ * - parens are transparent
22
+ *
23
+ * Multiple narrowings on the same variable intersect (most restrictive wins).
24
+ */
25
+ export function extractIsNarrowings(expr, polarity) {
26
+ const acc = [];
27
+ collect(expr, polarity, acc);
28
+ return mergeIntersect(acc);
29
+ }
30
+ function collect(expr, polarity, acc) {
31
+ if (expr instanceof TirParentesizedExpr) {
32
+ collect(expr.expr, polarity, acc);
33
+ return;
34
+ }
35
+ if (expr instanceof TirUnaryExclamation) {
36
+ collect(expr.operand, !polarity, acc);
37
+ return;
38
+ }
39
+ const ctorName = expr?.constructor?.name;
40
+ if (ctorName === "TirLogicalAndExpr") {
41
+ if (polarity) {
42
+ collect(expr.left, true, acc);
43
+ collect(expr.right, true, acc);
44
+ }
45
+ // FALSE side of `&&` tells us nothing
46
+ return;
47
+ }
48
+ if (ctorName === "TirLogicalOrExpr") {
49
+ if (!polarity) {
50
+ collect(expr.left, false, acc);
51
+ collect(expr.right, false, acc);
52
+ }
53
+ // TRUE side of `||` tells us nothing
54
+ return;
55
+ }
56
+ if (expr instanceof TirIsExpr) {
57
+ const inst = expr.instanceExpr;
58
+ if (!(inst instanceof TirVariableAccessExpr))
59
+ return;
60
+ const parentStructType = getStructType(inst.type);
61
+ if (!parentStructType)
62
+ return;
63
+ if (polarity) {
64
+ acc.push({
65
+ varName: inst.varName,
66
+ parentCtorIdxs: [expr.parentCtorIdx],
67
+ parentStructType
68
+ });
69
+ }
70
+ else {
71
+ // narrow to all OTHER parent constructor indexes still possible
72
+ const baseIdxs = parentStructType.narrowedFromParentCtorIdxs
73
+ ?? parentStructType.constructors.map((_, i) => i);
74
+ const remaining = baseIdxs.filter(idx => idx !== expr.parentCtorIdx);
75
+ if (remaining.length === 0)
76
+ return; // narrowed away entirely; type unchanged
77
+ acc.push({
78
+ varName: inst.varName,
79
+ parentCtorIdxs: remaining,
80
+ parentStructType
81
+ });
82
+ }
83
+ return;
84
+ }
85
+ }
86
+ /**
87
+ * Merge multiple narrowings: per variable, take the intersection of
88
+ * possible parent constructor indexes (most restrictive).
89
+ */
90
+ function mergeIntersect(narrowings) {
91
+ const byVar = new Map();
92
+ for (const n of narrowings) {
93
+ const existing = byVar.get(n.varName);
94
+ if (!existing) {
95
+ byVar.set(n.varName, { ...n, parentCtorIdxs: [...n.parentCtorIdxs] });
96
+ continue;
97
+ }
98
+ existing.parentCtorIdxs = existing.parentCtorIdxs.filter(idx => n.parentCtorIdxs.includes(idx));
99
+ }
100
+ return [...byVar.values()].filter(n => n.parentCtorIdxs.length > 0);
101
+ }
102
+ export function applyNarrowingsToScope(scope, narrowings) {
103
+ for (const n of narrowings) {
104
+ if (!(n.parentStructType instanceof TirDataStructType)
105
+ && !(n.parentStructType instanceof TirSoPStructType))
106
+ continue;
107
+ const narrowed = n.parentStructType.narrowTo(n.parentCtorIdxs);
108
+ scope.narrowVariable(n.varName, narrowed);
109
+ }
110
+ }
@@ -1,4 +1,4 @@
1
- import { compileUPLC, parseUPLC, UPLCProgram } from "@harmoniclabs/uplc";
1
+ import { compileUPLC, Force, parseUPLC, UPLCProgram } from "@harmoniclabs/uplc";
2
2
  import { Machine } from "@harmoniclabs/plutus-machine";
3
3
  import { DiagnosticEmitter } from "../diagnostics/DiagnosticEmitter.js";
4
4
  import { defaultOptions } from "../IR/toUPLC/CompilerOptions.js";
@@ -98,7 +98,9 @@ export class Compiler extends DiagnosticEmitter {
98
98
  }
99
99
  const serialized = this._compileBackend(cfg, program);
100
100
  const uplcProgram = parseUPLC(serialized);
101
- return Machine.eval(uplcProgram.body);
101
+ // the run-wrapper is always a 0-arg function, which now
102
+ // compiles to `Delay(body)`; force it so the body executes.
103
+ return Machine.eval(new Force(uplcProgram.body));
102
104
  }
103
105
  async runRepl(config) {
104
106
  const cfg = {
@@ -120,7 +122,9 @@ export class Compiler extends DiagnosticEmitter {
120
122
  }
121
123
  const serialized = this._compileBackend(cfg, program);
122
124
  const uplcProgram = parseUPLC(serialized);
123
- return Machine.eval(uplcProgram.body);
125
+ // the repl-wrapper is always a 0-arg function, which now
126
+ // compiles to `Delay(body)`; force it so the body executes.
127
+ return Machine.eval(new Force(uplcProgram.body));
124
128
  }
125
129
  _compileBackend(cfg, program) {
126
130
  // backend starts here
@@ -15,6 +15,7 @@ import { TirLitVoidExpr } from "../../tir/expressions/litteral/TirLitVoidExpr.js
15
15
  import { TirAssertAndContinueExpr } from "../../tir/expressions/TirAssertAndContinueExpr.js";
16
16
  import { TirCallExpr } from "../../tir/expressions/TirCallExpr.js";
17
17
  import { TirCaseExpr, TirCaseMatcher, TirWildcardCaseMatcher } from "../../tir/expressions/TirCaseExpr.js";
18
+ import { TirIsExpr } from "../../tir/expressions/TirIsExpr.js";
18
19
  import { TirElemAccessExpr } from "../../tir/expressions/TirElemAccessExpr.js";
19
20
  import { TirFailExpr } from "../../tir/expressions/TirFailExpr.js";
20
21
  import { TirFromDataExpr } from "../../tir/expressions/TirFromDataExpr.js";
@@ -182,6 +183,10 @@ export function expressifyVars(ctx, expr) {
182
183
  expr.ifFalse = ifFalse;
183
184
  return expr;
184
185
  }
186
+ if (expr instanceof TirIsExpr) {
187
+ expr.instanceExpr = expressifyVars(ctx, expr.instanceExpr);
188
+ return expr;
189
+ }
185
190
  if (isTirBinaryExpr(expr)) {
186
191
  const left = expressifyVars(ctx, expr.left);
187
192
  const right = expressifyVars(ctx, expr.right);
@@ -260,6 +265,7 @@ function expressifyPropAccess(ctx, propAccessExpr) {
260
265
  || expr instanceof TirPropAccessExpr // `expressifyVars` is called recursively on the object before this check
261
266
  // these result to native types
262
267
  || expr instanceof TirUnaryExclamation // boolean
268
+ || expr instanceof TirIsExpr // boolean
263
269
  || expr instanceof TirUnaryPlus // int
264
270
  || expr instanceof TirUnaryMinus // int
265
271
  || expr instanceof TirUnaryTilde // bytes
@@ -1,4 +1,5 @@
1
1
  import { _ir_apps } from "../../../IR/IRNodes/IRApp.js";
2
+ import { IRForced } from "../../../IR/IRNodes/IRForced.js";
2
3
  import { mergeSortedStrArrInplace } from "../../../utils/array/mergeSortedStrArrInplace.js";
3
4
  export class TirCallExpr {
4
5
  func;
@@ -38,6 +39,9 @@ export class TirCallExpr {
38
39
  }
39
40
  get isConstant() { return false; }
40
41
  toIR(ctx) {
42
+ if (this.args.length === 0) {
43
+ return new IRForced(this.func.toIR(ctx));
44
+ }
41
45
  return _ir_apps(this.func.toIR(ctx), ...this.args.map(arg => arg.toIR(ctx)));
42
46
  }
43
47
  }
@@ -112,7 +112,14 @@ export class TirCaseExpr {
112
112
  }
113
113
  // TirSopStructType
114
114
  const wildcardBodyIR = this.wildcardCase?.body.toIR(ctx) ?? new IRError();
115
- const branches = matchExprType.constructors.map((ctor, ctorIdx) => {
115
+ // For narrowed SoP types, `matchExprType.constructors` may be a strict
116
+ // subset of the runtime universe. The IRCase branch array must be
117
+ // indexed by runtime (parent) ctor index, so we build it sized to the
118
+ // largest parent index we know about and fill missing slots with the
119
+ // wildcard / IRError.
120
+ const sopType = matchExprType instanceof TirSopOptT ? undefined : matchExprType;
121
+ const sopParentIdxs = sopType?.narrowedFromParentCtorIdxs;
122
+ const localBranches = matchExprType.constructors.map((ctor, ctorIdx) => {
116
123
  const nFields = ctor.fields.length;
117
124
  const branchCtx = ctx.newChild();
118
125
  const branch = this.cases.find(c => c.pattern.constrName === ctor.name);
@@ -142,6 +149,21 @@ export class TirCaseExpr {
142
149
  return branch.body.toIR(branchCtx);
143
150
  return new IRFunc(introducedVars, branch.body.toIR(branchCtx));
144
151
  });
152
+ // Map local branches into runtime-indexed slots. For un-narrowed types
153
+ // this is identity.
154
+ let branches;
155
+ if (sopParentIdxs) {
156
+ const maxParent = sopParentIdxs.reduce((m, x) => x > m ? x : m, -1);
157
+ branches = new Array(maxParent + 1);
158
+ for (let i = 0; i < branches.length; i++)
159
+ branches[i] = wildcardBodyIR;
160
+ for (let localIdx = 0; localIdx < localBranches.length; localIdx++) {
161
+ branches[sopParentIdxs[localIdx]] = localBranches[localIdx];
162
+ }
163
+ }
164
+ else {
165
+ branches = localBranches;
166
+ }
145
167
  // branches at the end that are supposed to "just fail"
146
168
  // can be omitted, as the CEK machine will fail if no branch for
147
169
  // a given constructor is found
@@ -202,9 +224,12 @@ export class TirCaseExpr {
202
224
  .filter(fName => pattern.fields.has(fName)));
203
225
  if (usedFieldsCtorNames.length <= 0) {
204
226
  ifThenElseMatchingStatements = _ir_lazyIfThenElse(
205
- // condition
227
+ // condition (compare against the PARENT ctor index — the
228
+ // value's runtime tag is unaffected by narrowing)
206
229
  _ir_apps(new IRVar(isConstrIdxSym), // isConstrIdx
207
- IRConst.int(ctorIdx)),
230
+ IRConst.int(matchExprType instanceof TirDataStructType
231
+ ? matchExprType.parentCtorIdx(ctorIdx)
232
+ : ctorIdx)),
208
233
  // then
209
234
  body.toIR(stmtCtx),
210
235
  // else
@@ -229,9 +254,12 @@ export class TirCaseExpr {
229
254
  IRNative.sndPair, new IRVar(unConstrStructSym) // unconstrStruct
230
255
  )))), body.toIR(thenCtx));
231
256
  ifThenElseMatchingStatements = _ir_lazyIfThenElse(
232
- // condition
257
+ // condition (compare against the PARENT ctor index — the
258
+ // value's runtime tag is unaffected by narrowing)
233
259
  _ir_apps(new IRVar(isConstrIdxSym), // isConstrIdx
234
- IRConst.int(ctorIdx)),
260
+ IRConst.int(matchExprType instanceof TirDataStructType
261
+ ? matchExprType.parentCtorIdx(ctorIdx)
262
+ : ctorIdx)),
235
263
  // then
236
264
  thenCase,
237
265
  // else
@@ -257,9 +285,12 @@ export class TirCaseExpr {
257
285
  ), // fileds as data list
258
286
  _ir_apps(new IRFunc(introducedVars, body.toIR(thenCtx)), ...extractedFields));
259
287
  ifThenElseMatchingStatements = _ir_lazyIfThenElse(
260
- // condition
288
+ // condition (compare against the PARENT ctor index — the
289
+ // value's runtime tag is unaffected by narrowing)
261
290
  _ir_apps(new IRVar(isConstrIdxSym), // isConstrIdx
262
- IRConst.int(ctorIdx)),
291
+ IRConst.int(matchExprType instanceof TirDataStructType
292
+ ? matchExprType.parentCtorIdx(ctorIdx)
293
+ : ctorIdx)),
263
294
  // then
264
295
  thenCase,
265
296
  // else
@@ -20,5 +20,6 @@ import { TirTraceIfFalseExpr } from "./TirTraceIfFalseExpr.js";
20
20
  import { TirTraceExpr } from "./TirTraceExpr.js";
21
21
  import { TirNativeFunc } from "./TirNativeFunc.js";
22
22
  import { TirInlineClosedIR } from "./TirInlineClosedIR.js";
23
- export type TirExpr = (TirUnaryPrefixExpr | TirLitteralExpr | TirParentesizedExpr | TirFuncExpr | TirCallExpr | TirCaseExpr | TirTypeConversionExpr | TirElemAccessExpr | TirTernaryExpr | TirPropAccessExpr | TirBinaryExpr | TirVariableAccessExpr | TirLettedExpr | TirNativeFunc | TirFailExpr | TirHoistedExpr | TirFromDataExpr | TirToDataExpr | TirAssertAndContinueExpr | TirTraceIfFalseExpr | TirTraceExpr | TirInlineClosedIR);
23
+ import { TirIsExpr } from "./TirIsExpr.js";
24
+ export type TirExpr = (TirUnaryPrefixExpr | TirLitteralExpr | TirParentesizedExpr | TirFuncExpr | TirCallExpr | TirCaseExpr | TirTypeConversionExpr | TirIsExpr | TirElemAccessExpr | TirTernaryExpr | TirPropAccessExpr | TirBinaryExpr | TirVariableAccessExpr | TirLettedExpr | TirNativeFunc | TirFailExpr | TirHoistedExpr | TirFromDataExpr | TirToDataExpr | TirAssertAndContinueExpr | TirTraceIfFalseExpr | TirTraceExpr | TirInlineClosedIR);
24
25
  export declare function isTirExpr(thing: any): thing is TirExpr;
@@ -21,6 +21,7 @@ import { TirTraceIfFalseExpr } from "./TirTraceIfFalseExpr.js";
21
21
  import { TirTraceExpr } from "./TirTraceExpr.js";
22
22
  import { TirNativeFunc } from "./TirNativeFunc.js";
23
23
  import { TirInlineClosedIR } from "./TirInlineClosedIR.js";
24
+ import { TirIsExpr } from "./TirIsExpr.js";
24
25
  export function isTirExpr(thing) {
25
26
  return isObject(thing) && (isTirUnaryPrefixExpr(thing)
26
27
  || isTirLitteralExpr(thing)
@@ -29,7 +30,7 @@ export function isTirExpr(thing) {
29
30
  || thing instanceof TirCallExpr
30
31
  || thing instanceof TirCaseExpr
31
32
  || thing instanceof TirTypeConversionExpr
32
- // || thing instanceof TirIsExpr
33
+ || thing instanceof TirIsExpr
33
34
  || thing instanceof TirElemAccessExpr
34
35
  || thing instanceof TirTernaryExpr
35
36
  || thing instanceof TirPropAccessExpr
@@ -4,6 +4,7 @@ import { IRRecursive } from "../../../IR/IRNodes/IRRecursive.js";
4
4
  import { TirReturnStmt } from "../statements/TirReturnStmt.js";
5
5
  import { TirFuncT } from "../types/TirNativeType/native/function.js";
6
6
  import { IRFunc } from "../../../IR/IRNodes/IRFunc.js";
7
+ import { IRDelayed } from "../../../IR/IRNodes/IRDelayed.js";
7
8
  export class TirFuncExpr {
8
9
  name;
9
10
  params;
@@ -101,7 +102,9 @@ export class TirFuncExpr {
101
102
  if (isRecursive)
102
103
  recursiveVarSym = ctx.defineRecursiveVar(this.name);
103
104
  const introuducedVars = this.params.map(param => ctx.defineVar(param.name));
104
- let irFunc = new IRFunc(introuducedVars, expr.toIR(ctx));
105
+ let irFunc = this.params.length === 0
106
+ ? new IRDelayed(expr.toIR(ctx))
107
+ : new IRFunc(introuducedVars, expr.toIR(ctx));
105
108
  if (isRecursive)
106
109
  irFunc = new IRRecursive(recursiveVarSym, irFunc);
107
110
  return irFunc;
@@ -0,0 +1,23 @@
1
+ import { SourceRange } from "../../../ast/Source/SourceRange.js";
2
+ import type { IRTerm } from "../../../IR/IRTerm.js";
3
+ import { TirBoolT } from "../types/TirNativeType/native/bool.js";
4
+ import { ITirExpr } from "./ITirExpr.js";
5
+ import { TirExpr } from "./TirExpr.js";
6
+ import { ToIRTermCtx } from "./ToIRTermCtx.js";
7
+ export declare class TirIsExpr implements ITirExpr {
8
+ instanceExpr: TirExpr;
9
+ readonly ctorName: string;
10
+ /** index in the *parent* (un-narrowed) struct's `constructors` array */
11
+ readonly parentCtorIdx: number;
12
+ readonly range: SourceRange;
13
+ readonly type: TirBoolT;
14
+ constructor(instanceExpr: TirExpr, ctorName: string,
15
+ /** index in the *parent* (un-narrowed) struct's `constructors` array */
16
+ parentCtorIdx: number, range: SourceRange, boolType: TirBoolT);
17
+ toString(): string;
18
+ pretty(indent: number): string;
19
+ clone(): TirIsExpr;
20
+ deps(): string[];
21
+ get isConstant(): boolean;
22
+ toIR(ctx: ToIRTermCtx): IRTerm;
23
+ }
@@ -0,0 +1,87 @@
1
+ import { _ir_apps } from "../../../IR/IRNodes/IRApp.js";
2
+ import { IRCase } from "../../../IR/IRNodes/IRCase.js";
3
+ import { IRConst } from "../../../IR/IRNodes/IRConst.js";
4
+ import { IRFunc } from "../../../IR/IRNodes/IRFunc.js";
5
+ import { IRNative } from "../../../IR/IRNodes/IRNative/index.js";
6
+ import { TirDataStructType, TirSoPStructType } from "../types/TirStructType.js";
7
+ import { getUnaliased } from "../types/utils/getUnaliased.js";
8
+ export class TirIsExpr {
9
+ instanceExpr;
10
+ ctorName;
11
+ parentCtorIdx;
12
+ range;
13
+ type;
14
+ constructor(instanceExpr, ctorName,
15
+ /** index in the *parent* (un-narrowed) struct's `constructors` array */
16
+ parentCtorIdx, range, boolType) {
17
+ this.instanceExpr = instanceExpr;
18
+ this.ctorName = ctorName;
19
+ this.parentCtorIdx = parentCtorIdx;
20
+ this.range = range;
21
+ this.type = boolType;
22
+ }
23
+ toString() {
24
+ return `(${this.instanceExpr.toString()} is ${this.ctorName})`;
25
+ }
26
+ pretty(indent) {
27
+ return `(${this.instanceExpr.pretty(indent)} is ${this.ctorName})`;
28
+ }
29
+ clone() {
30
+ return new TirIsExpr(this.instanceExpr.clone(), this.ctorName, this.parentCtorIdx, this.range.clone(), this.type.clone());
31
+ }
32
+ deps() {
33
+ return this.instanceExpr.deps();
34
+ }
35
+ get isConstant() { return this.instanceExpr.isConstant; }
36
+ toIR(ctx) {
37
+ const structType = getUnaliased(this.instanceExpr.type);
38
+ if (structType instanceof TirDataStructType) {
39
+ // equalsInteger(parentCtorIdx, fstPair(unConstrData(<instance>)))
40
+ return _ir_apps(IRNative.equalsInteger, IRConst.int(this.parentCtorIdx), _ir_apps(IRNative.fstPair, _ir_apps(IRNative.unConstrData, this.instanceExpr.toIR(ctx))));
41
+ }
42
+ if (structType instanceof TirSoPStructType) {
43
+ // we need a branch in the IRCase per ORIGINAL parent constructor.
44
+ // the type system has narrowed `instanceExpr.type`, but at runtime
45
+ // the value is still a Constr from the parent universe; if the
46
+ // current type is narrowed, we use its parent-idx mapping.
47
+ const localToParent = structType.narrowedFromParentCtorIdxs
48
+ ?? structType.constructors.map((_, i) => i);
49
+ // We need to size the IRCase to the maximum parent index we know about.
50
+ // For pure narrowing of an SoP value at runtime, we still see ALL
51
+ // parent constructors. Without access to the un-narrowed type here,
52
+ // we trust the constructor list reflects the runtime layout when
53
+ // `structType` is un-narrowed; when narrowed we still ALSO need to
54
+ // produce a complete IRCase for the underlying value.
55
+ //
56
+ // For the un-narrowed case (structType.constructors enumerates all
57
+ // runtime variants) this is correct. For a narrowed SoP (where the
58
+ // local constructors array is smaller than the runtime arity), we
59
+ // must emit a branch for every original parent slot up to `max + 1`
60
+ // and treat anything outside the narrowed set as "false".
61
+ const maxParentIdx = localToParent.reduce((m, x) => x > m ? x : m, -1);
62
+ const branchCount = maxParentIdx + 1;
63
+ const branches = new Array(branchCount);
64
+ for (let parentIdx = 0; parentIdx < branchCount; parentIdx++) {
65
+ // find the local constructor for this parent index, if any
66
+ const localIdx = localToParent.indexOf(parentIdx);
67
+ const matches = parentIdx === this.parentCtorIdx;
68
+ const body = IRConst.bool(matches);
69
+ // we don't know the field arity for parent slots that have
70
+ // been narrowed away. Use the local ctor if present;
71
+ // otherwise no params (best-effort: narrowed-away slots
72
+ // shouldn't be reachable at runtime in well-typed code).
73
+ const fields = localIdx >= 0 ? structType.constructors[localIdx].fields.length : 0;
74
+ if (fields <= 0) {
75
+ branches[parentIdx] = body;
76
+ }
77
+ else {
78
+ const introduced = Array(fields).fill(0).map(() => Symbol("_"));
79
+ branches[parentIdx] = new IRFunc(introduced, body);
80
+ }
81
+ }
82
+ return new IRCase(this.instanceExpr.toIR(ctx), branches);
83
+ }
84
+ throw new Error("`is` operator can only be applied to struct types; got: "
85
+ + this.instanceExpr.type.toString());
86
+ }
87
+ }