@astrale-os/kernel-dsl 0.2.0-beta.41 → 0.2.0-beta.43
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.
- package/dist/v1/compiled/admission/semantics.js +19 -4
- package/dist/v1/language/policy/analysis/checks.d.ts +28 -0
- package/dist/v1/language/policy/analysis/checks.js +80 -0
- package/dist/v1/language/policy/normalization/normalize.js +50 -33
- package/dist/v1/schema/validation/policy/typing.d.ts +2 -0
- package/dist/v1/schema/validation/policy/typing.js +5 -1
- package/dist/v1/schema/validation/policy/uses.js +20 -8
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { isMethodExecutable } from '../../language/callable/executable.js';
|
|
|
6
6
|
import { policyObjectKey, policyObjectOrigin, policyVariableClass, projectedMemberClass, } from '../../language/index.js';
|
|
7
7
|
import { branchesOf, branchProduct, capBranches, isConnected, targetMode, termKey, } from '../../language/policy/analysis/branches.js';
|
|
8
8
|
import { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks, } from '../../language/policy/analysis/budgets.js';
|
|
9
|
+
import { admittedCheckClasses, neverAdmittedClasses, } from '../../language/policy/analysis/checks.js';
|
|
9
10
|
import { normalizePolicyExpression } from '../../language/policy/normalization/normalize.js';
|
|
10
11
|
import { bindValueProgram } from '../../language/value/compile/load.js';
|
|
11
12
|
import { validateValueProgram } from '../../language/value/evaluate/program.js';
|
|
@@ -538,6 +539,7 @@ function validateCallablePolicyTyping(context, typing, entry, expression, input,
|
|
|
538
539
|
measure.leaves > MAX_CHECK_LEAVES) {
|
|
539
540
|
fail(path, 'Callable Policy checks exceed the V1 depth, branch, or leaf budget.');
|
|
540
541
|
}
|
|
542
|
+
const leaves = new Map();
|
|
541
543
|
const visit = (value, pointer) => {
|
|
542
544
|
if ('sameNode' in value)
|
|
543
545
|
return;
|
|
@@ -547,12 +549,23 @@ function validateCallablePolicyTyping(context, typing, entry, expression, input,
|
|
|
547
549
|
return;
|
|
548
550
|
}
|
|
549
551
|
const contracts = callableObjectContracts(context, entry, input, receiver, value.object);
|
|
550
|
-
if (contracts
|
|
551
|
-
|
|
552
|
+
if (contracts === undefined)
|
|
553
|
+
return;
|
|
554
|
+
const typings = new Map(contracts.map((contract) => [
|
|
555
|
+
contract.kind === 'exact' ? Key.of(contract.ref) : 'default',
|
|
556
|
+
typing.policyBranchTyping(value.check.name, contract),
|
|
557
|
+
]));
|
|
558
|
+
const admitted = admittedCheckClasses(typings);
|
|
559
|
+
if (admitted === undefined) {
|
|
552
560
|
fail(pointer, 'Named Policy is incompatible with the callable object contract.');
|
|
553
561
|
}
|
|
562
|
+
leaves.set(value, { accepted: [...typings.keys()], admitted });
|
|
554
563
|
};
|
|
555
564
|
visit(expression, path);
|
|
565
|
+
const [never] = neverAdmittedClasses(expression, leaves);
|
|
566
|
+
if (never !== undefined) {
|
|
567
|
+
fail(path, `Callable Policy checks can never admit ${never[0]} of Class ${never[1]}.`);
|
|
568
|
+
}
|
|
556
569
|
}
|
|
557
570
|
function callableObjectContracts(context, entry, input, receiver, object) {
|
|
558
571
|
if (object.kind === 'self') {
|
|
@@ -629,9 +642,11 @@ class CompiledPolicyTyping {
|
|
|
629
642
|
return 'allOf' in declaration ? branchProduct(children) : capBranches(children.flat());
|
|
630
643
|
}
|
|
631
644
|
policyAccepts(name, object) {
|
|
645
|
+
return this.policyBranchTyping(name, object)?.every(Boolean) ?? false;
|
|
646
|
+
}
|
|
647
|
+
policyBranchTyping(name, object) {
|
|
632
648
|
const branches = this.expandedBranches(name, new Set(), `$use:${name}`);
|
|
633
|
-
return (
|
|
634
|
-
branches.every((branch) => this.branchIsTyped(branch, { kind: 'object', object })));
|
|
649
|
+
return branches?.map((branch) => this.branchIsTyped(branch, { kind: 'object', object }));
|
|
635
650
|
}
|
|
636
651
|
policyAcceptsEdge(name, source, target) {
|
|
637
652
|
const branches = this.expandedBranches(name, new Set(), `$edge-use:${name}`);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PolicyCheckExpression } from '../model/policy.js';
|
|
2
|
+
export type PolicyCheckLeaf = Extract<PolicyCheckExpression, {
|
|
3
|
+
readonly check: unknown;
|
|
4
|
+
}>;
|
|
5
|
+
/** How one check leaf types against the Classes its object accepts. */
|
|
6
|
+
export interface CheckLeafClasses {
|
|
7
|
+
/** Every Class the leaf's object accepts. */
|
|
8
|
+
readonly accepted: readonly string[];
|
|
9
|
+
/** The accepted Classes at least one normalized branch of the leaf's Policy is typed for. */
|
|
10
|
+
readonly admitted: ReadonlySet<string>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The Classes a leaf admits, from the typing of each normalized branch of its Policy for each
|
|
14
|
+
* accepted Class of its object. `undefined` when the Policy does not expand, or when one branch is
|
|
15
|
+
* typed for no accepted Class: that branch can never match this object, so it is refused rather
|
|
16
|
+
* than silently erased.
|
|
17
|
+
*/
|
|
18
|
+
export declare function admittedCheckClasses(typing: ReadonlyMap<string, readonly boolean[] | undefined>): ReadonlySet<string> | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* The accepted Classes of each object that the whole check tree can never admit.
|
|
21
|
+
*
|
|
22
|
+
* The tree is read in disjunctive normal form. A conjunction admits Class C of object O when every
|
|
23
|
+
* leaf over O in it admits C and every other object it checks keeps a Class that all of its leaves
|
|
24
|
+
* over that object admit; an object the conjunction does not check keeps every accepted Class.
|
|
25
|
+
* `sameNode` compares identities, not Classes, so it never removes a Class here. A tree over the
|
|
26
|
+
* check budget is already refused, so it is not expanded.
|
|
27
|
+
*/
|
|
28
|
+
export declare function neverAdmittedClasses(expression: PolicyCheckExpression, leaves: ReadonlyMap<PolicyCheckLeaf, CheckLeafClasses>): readonly (readonly [object: string, className: string])[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { policyObjectKey } from '../model/policy.js';
|
|
2
|
+
import { MAX_BRANCHES, measureChecks } from './budgets.js';
|
|
3
|
+
/** Names a callable check object. Every leaf over one object sees the same Node at runtime. */
|
|
4
|
+
function checkObjectName(object) {
|
|
5
|
+
if (object.kind === 'self')
|
|
6
|
+
return 'self';
|
|
7
|
+
if (object.kind === 'input')
|
|
8
|
+
return `input.${object.field}`;
|
|
9
|
+
return policyObjectKey(object.ref);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The Classes a leaf admits, from the typing of each normalized branch of its Policy for each
|
|
13
|
+
* accepted Class of its object. `undefined` when the Policy does not expand, or when one branch is
|
|
14
|
+
* typed for no accepted Class: that branch can never match this object, so it is refused rather
|
|
15
|
+
* than silently erased.
|
|
16
|
+
*/
|
|
17
|
+
export function admittedCheckClasses(typing) {
|
|
18
|
+
const rows = [...typing];
|
|
19
|
+
if (!rows.every((row) => row[1] !== undefined)) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const branches = Math.max(0, ...rows.map(([, row]) => row.length));
|
|
23
|
+
for (let index = 0; index < branches; index++) {
|
|
24
|
+
if (!rows.some(([, row]) => row[index] === true))
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return new Set(rows.filter(([, row]) => row.some(Boolean)).map(([name]) => name));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The accepted Classes of each object that the whole check tree can never admit.
|
|
31
|
+
*
|
|
32
|
+
* The tree is read in disjunctive normal form. A conjunction admits Class C of object O when every
|
|
33
|
+
* leaf over O in it admits C and every other object it checks keeps a Class that all of its leaves
|
|
34
|
+
* over that object admit; an object the conjunction does not check keeps every accepted Class.
|
|
35
|
+
* `sameNode` compares identities, not Classes, so it never removes a Class here. A tree over the
|
|
36
|
+
* check budget is already refused, so it is not expanded.
|
|
37
|
+
*/
|
|
38
|
+
export function neverAdmittedClasses(expression, leaves) {
|
|
39
|
+
if (measureChecks(expression).branches > MAX_BRANCHES)
|
|
40
|
+
return [];
|
|
41
|
+
const accepted = new Map();
|
|
42
|
+
for (const [leaf, classes] of leaves)
|
|
43
|
+
accepted.set(checkObjectName(leaf.object), classes.accepted);
|
|
44
|
+
const admitted = new Map();
|
|
45
|
+
for (const conjunction of disjunctiveForm(expression)) {
|
|
46
|
+
const constrained = new Map();
|
|
47
|
+
for (const leaf of conjunction) {
|
|
48
|
+
if (!('check' in leaf))
|
|
49
|
+
continue;
|
|
50
|
+
const classes = leaves.get(leaf);
|
|
51
|
+
if (classes === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
const object = checkObjectName(leaf.object);
|
|
54
|
+
const previous = constrained.get(object);
|
|
55
|
+
constrained.set(object, previous === undefined
|
|
56
|
+
? classes.admitted
|
|
57
|
+
: new Set([...previous].filter((name) => classes.admitted.has(name))));
|
|
58
|
+
}
|
|
59
|
+
if ([...constrained.values()].some((classes) => classes.size === 0))
|
|
60
|
+
continue;
|
|
61
|
+
for (const [object, all] of accepted) {
|
|
62
|
+
const reached = admitted.get(object) ?? new Set();
|
|
63
|
+
for (const name of constrained.get(object) ?? all)
|
|
64
|
+
reached.add(name);
|
|
65
|
+
admitted.set(object, reached);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return [...accepted].flatMap(([object, all]) => all
|
|
69
|
+
.filter((name) => admitted.get(object)?.has(name) !== true)
|
|
70
|
+
.map((name) => [object, name]));
|
|
71
|
+
}
|
|
72
|
+
function disjunctiveForm(expression) {
|
|
73
|
+
if ('check' in expression || 'sameNode' in expression)
|
|
74
|
+
return [[expression]];
|
|
75
|
+
if ('anyOf' in expression)
|
|
76
|
+
return expression.anyOf.flatMap(disjunctiveForm);
|
|
77
|
+
return expression.allOf
|
|
78
|
+
.map(disjunctiveForm)
|
|
79
|
+
.reduce((left, right) => left.flatMap((prefix) => right.map((suffix) => [...prefix, ...suffix])), [[]]);
|
|
80
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { definitionRefIndex, compareDefinitionRefs } from '../../../addressing/ordering.js';
|
|
3
3
|
import { policyObjectKey, policyVariableSelector } from '../model/policy.js';
|
|
4
|
-
const textEncoder = new TextEncoder();
|
|
5
4
|
export function normalizePolicyExpression(value) {
|
|
6
5
|
if ('match' in value) {
|
|
7
6
|
return { match: normalizePolicyPattern(value.match) };
|
|
@@ -73,20 +72,22 @@ function normalizePatternNode(value) {
|
|
|
73
72
|
function canonicalVariableIds(value) {
|
|
74
73
|
const declarations = new Map();
|
|
75
74
|
collectDeclarations(value, declarations);
|
|
76
|
-
const
|
|
75
|
+
const uses = new Map([...declarations.keys()].map((id) => [id, []]));
|
|
76
|
+
collectUses(value, uses);
|
|
77
|
+
let colors = declarations;
|
|
78
|
+
// Color refinement. Each round names a variable by its declaration and the sorted colors of its
|
|
79
|
+
// uses, then replaces every name by its fixed-width rank among the round's names. Ranks keep the
|
|
80
|
+
// names' byte order, so a round decides what it would decide over uncompressed names, while a
|
|
81
|
+
// name stays linear in its variable's degree instead of embedding every neighbour's name. A
|
|
82
|
+
// round that reproduces its input is a fixed point of the next ones.
|
|
77
83
|
for (let iteration = 0; iteration < declarations.size + 1; iteration++) {
|
|
78
|
-
const next = new Map()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
uses.sort(compareUtf8Bytes);
|
|
83
|
-
next.set(id, `${declarations.get(id) ?? ''}|${uses.join('|')}`);
|
|
84
|
-
}
|
|
84
|
+
const next = rank(new Map([...uses].map(([id, list]) => {
|
|
85
|
+
const named = list.map(([prefix, term]) => `${prefix}${termColor(term, colors)}`);
|
|
86
|
+
return [id, `${declarations.get(id) ?? ''}|${named.sort(compareUtf8Bytes).join('|')}`];
|
|
87
|
+
})));
|
|
85
88
|
if ([...next].every(([id, color]) => colors.get(id) === color))
|
|
86
89
|
break;
|
|
87
|
-
colors
|
|
88
|
-
for (const [id, color] of next)
|
|
89
|
-
colors.set(id, color);
|
|
90
|
+
colors = next;
|
|
90
91
|
}
|
|
91
92
|
const firstUse = new Map();
|
|
92
93
|
collectFirstUseKeys(value, colors, firstUse);
|
|
@@ -118,29 +119,34 @@ function collectDeclarations(value, result) {
|
|
|
118
119
|
for (const child of value.anyOf)
|
|
119
120
|
collectDeclarations(child, result);
|
|
120
121
|
}
|
|
121
|
-
function collectUses(value,
|
|
122
|
+
function collectUses(value, result) {
|
|
122
123
|
if ('sameNode' in value) {
|
|
123
124
|
const { left, right } = value.sameNode;
|
|
124
|
-
if (left.kind === 'variable'
|
|
125
|
-
result.push(
|
|
126
|
-
if (right.kind === 'variable'
|
|
127
|
-
result.push(
|
|
125
|
+
if (left.kind === 'variable')
|
|
126
|
+
result.get(left.id)?.push(['same:', right]);
|
|
127
|
+
if (right.kind === 'variable')
|
|
128
|
+
result.get(right.id)?.push(['same:', left]);
|
|
128
129
|
return;
|
|
129
130
|
}
|
|
130
131
|
if (isEdge(value)) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (value.target.kind === 'variable'
|
|
135
|
-
result.push(`t:${
|
|
136
|
-
}
|
|
132
|
+
const edge = definitionRefIndex(value.class);
|
|
133
|
+
if (value.source.kind === 'variable')
|
|
134
|
+
result.get(value.source.id)?.push([`s:${edge}:`, value.target]);
|
|
135
|
+
if (value.target.kind === 'variable')
|
|
136
|
+
result.get(value.target.id)?.push([`t:${edge}:`, value.source]);
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
if ('exists' in value)
|
|
140
|
-
return collectUses(value.exists.where,
|
|
140
|
+
return collectUses(value.exists.where, result);
|
|
141
141
|
const children = 'allOf' in value ? value.allOf : value.anyOf;
|
|
142
142
|
for (const child of children)
|
|
143
|
-
collectUses(child,
|
|
143
|
+
collectUses(child, result);
|
|
144
|
+
}
|
|
145
|
+
function rank(names) {
|
|
146
|
+
const distinct = [...new Set(names.values())].sort(compareUtf8Bytes);
|
|
147
|
+
const width = String(distinct.length).length;
|
|
148
|
+
const ranks = new Map(distinct.map((name, index) => [name, String(index).padStart(width, '0')]));
|
|
149
|
+
return new Map([...names].map(([id, name]) => [id, ranks.get(name) ?? '']));
|
|
144
150
|
}
|
|
145
151
|
function termColor(term, colors) {
|
|
146
152
|
if (term.kind === 'variable')
|
|
@@ -181,8 +187,10 @@ function collectFirstUseKeys(value, colors, result, prefix = '$') {
|
|
|
181
187
|
}
|
|
182
188
|
const key = 'allOf' in value ? 'allOf' : 'anyOf';
|
|
183
189
|
const operands = 'allOf' in value ? value.allOf : value.anyOf;
|
|
184
|
-
const children =
|
|
185
|
-
|
|
190
|
+
const children = operands
|
|
191
|
+
.map((child) => [patternSignature(child, colors), child])
|
|
192
|
+
.sort(([left], [right]) => compareUtf8Bytes(left, right));
|
|
193
|
+
children.forEach(([, child], index) => collectFirstUseKeys(child, colors, result, `${prefix}:${key}:${index}`));
|
|
186
194
|
}
|
|
187
195
|
function patternSignature(value, colors) {
|
|
188
196
|
if ('sameNode' in value)
|
|
@@ -255,15 +263,24 @@ function uniqueJson(values) {
|
|
|
255
263
|
.sort(([left], [right]) => compareUtf8Bytes(left, right))
|
|
256
264
|
.map(([, value]) => value);
|
|
257
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* UTF-8 byte order without encoding: it is code-point order, which UTF-16 code units already follow
|
|
268
|
+
* except that surrogates (U+10000 and above) sort after U+E000–U+FFFF.
|
|
269
|
+
*/
|
|
258
270
|
function compareUtf8Bytes(left, right) {
|
|
259
|
-
const
|
|
260
|
-
const b = textEncoder.encode(right);
|
|
261
|
-
const length = Math.min(a.length, b.length);
|
|
271
|
+
const length = Math.min(left.length, right.length);
|
|
262
272
|
for (let index = 0; index < length; index++) {
|
|
263
|
-
|
|
264
|
-
|
|
273
|
+
const a = left.charCodeAt(index);
|
|
274
|
+
const b = right.charCodeAt(index);
|
|
275
|
+
if (a !== b)
|
|
276
|
+
return codePointOrder(a) - codePointOrder(b);
|
|
265
277
|
}
|
|
266
|
-
return
|
|
278
|
+
return left.length - right.length;
|
|
279
|
+
}
|
|
280
|
+
function codePointOrder(unit) {
|
|
281
|
+
if (unit >= 0xd800 && unit <= 0xdfff)
|
|
282
|
+
return unit + 0x2000;
|
|
283
|
+
return unit >= 0xe000 ? unit - 0x800 : unit;
|
|
267
284
|
}
|
|
268
285
|
function isEdge(value) {
|
|
269
286
|
return 'source' in value;
|
|
@@ -12,6 +12,8 @@ export declare class PolicyTyping {
|
|
|
12
12
|
constructor(context: PolicyValidationContext);
|
|
13
13
|
validatePolicies(): void;
|
|
14
14
|
policyAccepts(name: string, object: ObjectContract): boolean;
|
|
15
|
+
/** Whether each normalized branch of the Policy is typed for an object of this contract. */
|
|
16
|
+
policyBranchTyping(name: string, object: ObjectContract): readonly boolean[] | undefined;
|
|
15
17
|
policyAcceptsEdge(name: string, source: ObjectContract, target: ObjectContract): boolean;
|
|
16
18
|
concreteEndpointClasses(accepts: readonly ClassRef[]): readonly ClassRef[];
|
|
17
19
|
endpointsOverlap(left: readonly ClassRef[], right: readonly ClassRef[]): boolean;
|
|
@@ -35,9 +35,13 @@ export class PolicyTyping {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
policyAccepts(name, object) {
|
|
38
|
+
return this.policyBranchTyping(name, object)?.every(Boolean) ?? false;
|
|
39
|
+
}
|
|
40
|
+
/** Whether each normalized branch of the Policy is typed for an object of this contract. */
|
|
41
|
+
policyBranchTyping(name, object) {
|
|
38
42
|
const branches = this.#expandedBranches(name, new Set(), `$use:${name}`);
|
|
39
43
|
const use = { kind: 'object', object };
|
|
40
|
-
return branches
|
|
44
|
+
return branches?.map((branch) => this.#branchIsTyped(branch, use));
|
|
41
45
|
}
|
|
42
46
|
policyAcceptsEdge(name, source, target) {
|
|
43
47
|
const branches = this.#expandedBranches(name, new Set(), `$edge-use:${name}`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Key } from '../../../addressing/index.js';
|
|
2
2
|
import { definitionRefIndex } from '../../../addressing/ordering.js';
|
|
3
3
|
import { isBinaryInput } from '../../../language/callable/model/callable.js';
|
|
4
|
+
import { admittedCheckClasses, neverAdmittedClasses, } from '../../../language/policy/analysis/checks.js';
|
|
4
5
|
import { normalizePolicyChecks } from '../../../language/policy/normalization/normalize.js';
|
|
5
6
|
import { GRAPH_NODE_PATH_SCHEMA_ID } from '../../../language/value/syntax/node-path/schema.js';
|
|
6
7
|
import { MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, measureChecks } from './budgets.js';
|
|
@@ -89,9 +90,13 @@ function validateChecks(validation, typing, expression, context) {
|
|
|
89
90
|
measure.leaves > MAX_CHECK_LEAVES) {
|
|
90
91
|
validation.add('PL_BUDGET', context.pointer, `Callable checks require depth <= ${MAX_CHECK_DEPTH}, branches <= ${MAX_BRANCHES}, and leaves <= ${MAX_CHECK_LEAVES}; got ${measure.depth}, ${measure.branches}, ${measure.leaves}.`);
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
+
const leaves = new Map();
|
|
94
|
+
visitChecks(validation, typing, normalized, context, context.pointer, leaves);
|
|
95
|
+
for (const [object, className] of neverAdmittedClasses(normalized, leaves)) {
|
|
96
|
+
validation.add('PL_USE', context.pointer, `Callable checks can never admit ${object} of Class ${className}.`, className);
|
|
97
|
+
}
|
|
93
98
|
}
|
|
94
|
-
function visitChecks(validation, typing, expression, context, pointer) {
|
|
99
|
+
function visitChecks(validation, typing, expression, context, pointer, leaves) {
|
|
95
100
|
if ('sameNode' in expression) {
|
|
96
101
|
for (const [side, object] of Object.entries(expression.sameNode)) {
|
|
97
102
|
const objectPointer = `${pointer}/sameNode/${side}`;
|
|
@@ -115,19 +120,26 @@ function visitChecks(validation, typing, expression, context, pointer) {
|
|
|
115
120
|
: referenceContracts(validation, expression.object.ref, objectPointer);
|
|
116
121
|
if (contracts === undefined)
|
|
117
122
|
return;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
const typings = new Map(contracts.map((contract) => [
|
|
124
|
+
contractName(contract),
|
|
125
|
+
typing.policyBranchTyping(expression.check.name, contract),
|
|
126
|
+
]));
|
|
127
|
+
const admitted = admittedCheckClasses(typings);
|
|
128
|
+
if (admitted === undefined) {
|
|
129
|
+
validation.add('PL_USE', pointer, `${formatDefinitionRef(expression.check)} is incompatible with the callable object contract.`, formatDefinitionRef(expression.check));
|
|
130
|
+
return;
|
|
123
131
|
}
|
|
132
|
+
leaves.set(expression, { accepted: [...typings.keys()], admitted });
|
|
124
133
|
return;
|
|
125
134
|
}
|
|
126
135
|
const { key, values } = checkOperands(expression);
|
|
127
136
|
if (values.length === 0) {
|
|
128
137
|
validation.add('PL_COMPOSITION', `${pointer}/${key}`, `${key} must be non-empty.`);
|
|
129
138
|
}
|
|
130
|
-
values.forEach((child, index) => visitChecks(validation, typing, child, context, `${pointer}/${key}/${index}
|
|
139
|
+
values.forEach((child, index) => visitChecks(validation, typing, child, context, `${pointer}/${key}/${index}`, leaves));
|
|
140
|
+
}
|
|
141
|
+
function contractName(contract) {
|
|
142
|
+
return contract.kind === 'exact' ? formatDefinitionRef(contract.ref) : 'default';
|
|
131
143
|
}
|
|
132
144
|
function selfContracts(validation, context, pointer) {
|
|
133
145
|
if (!context.instanceMethod || context.receiver === undefined) {
|