@rsconcept/domain 1.8.0 → 2.0.1

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 (80) hide show
  1. package/dist/{analyzer-DpE0qknt.d.ts → analyzer-iqAXyROE.d.ts} +2 -2
  2. package/dist/{arguments-extractor-Dd1lFom9.d.ts → arguments-extractor-ngatANBI.d.ts} +2 -2
  3. package/dist/{ast-annotations-CZCzs9Uk.d.ts → ast-annotations-D6KZDu12.d.ts} +2 -2
  4. package/dist/{calculator-Dt03dsnd.d.ts → calculator-k8BGIKmj.d.ts} +2 -2
  5. package/dist/diagnostics-Clo3mkba.d.ts +279 -0
  6. package/dist/diagnostics-DYMvTSev.js +532 -0
  7. package/dist/diagnostics-DYMvTSev.js.map +1 -0
  8. package/dist/index.d.ts +14 -15
  9. package/dist/index.js +7 -6
  10. package/dist/library/diagnostics.d.ts +2 -0
  11. package/dist/library/diagnostics.js +2 -0
  12. package/dist/library/index.d.ts +15 -6
  13. package/dist/library/index.js +41 -13
  14. package/dist/library/index.js.map +1 -1
  15. package/dist/library/oss-api.d.ts +1 -1
  16. package/dist/library/oss-api.js +1 -1
  17. package/dist/library/rsengine.d.ts +2 -3
  18. package/dist/library/rsengine.js +1 -1
  19. package/dist/library/rsform-api.d.ts +4 -4
  20. package/dist/library/rsform-api.js +1 -404
  21. package/dist/library/rsform.d.ts +2 -198
  22. package/dist/library/rsform.js.map +1 -1
  23. package/dist/library/rsmodel-api.d.ts +2 -3
  24. package/dist/library/rsmodel-api.js +2 -4
  25. package/dist/library/rsmodel-api.js.map +1 -1
  26. package/dist/library/rsmodel.d.ts +2 -46
  27. package/dist/library/structure-planner.d.ts +2 -2
  28. package/dist/library/structure-planner.js +1 -1
  29. package/dist/rslang/api.d.ts +1 -1
  30. package/dist/rslang/api.js +2 -1
  31. package/dist/rslang/api.js.map +1 -1
  32. package/dist/rslang/ast-annotations.d.ts +1 -1
  33. package/dist/rslang/eval/calculator.d.ts +1 -1
  34. package/dist/rslang/eval/evaluator.js +2 -2
  35. package/dist/rslang/eval/value-api.d.ts +1 -1
  36. package/dist/rslang/index.d.ts +8 -8
  37. package/dist/rslang/index.js +2 -2
  38. package/dist/rslang/labels.d.ts +4 -2
  39. package/dist/rslang/labels.js +8 -2
  40. package/dist/rslang/labels.js.map +1 -1
  41. package/dist/rslang/parser/syntax-errors/index.d.ts +1 -1
  42. package/dist/rslang/parser/syntax-errors/index.js +1 -1
  43. package/dist/rslang/semantic/analyzer.d.ts +1 -1
  44. package/dist/rslang/semantic/analyzer.js +3 -3
  45. package/dist/rslang/semantic/arguments-extractor.d.ts +1 -1
  46. package/dist/rslang/semantic/arguments-extractor.js +1 -1
  47. package/dist/rslang/semantic/type-auditor.d.ts +1 -1
  48. package/dist/rslang/semantic/type-auditor.js +4 -4
  49. package/dist/rslang/semantic/type-auditor.js.map +1 -1
  50. package/dist/rslang/semantic/typification-api.d.ts +1 -1
  51. package/dist/rslang/semantic/typification-parser.d.ts +1 -1
  52. package/dist/rslang/semantic/typification.d.ts +2 -2
  53. package/dist/rslang/semantic/typification.js +14 -6
  54. package/dist/rslang/semantic/typification.js.map +1 -1
  55. package/dist/rslang/semantic/value-auditor.js +2 -2
  56. package/dist/rslang/typification-graph.d.ts +1 -1
  57. package/dist/{syntax-errors-B6c2vsan.js → syntax-errors-D_VV5hAM.js} +3 -3
  58. package/dist/{syntax-errors-B6c2vsan.js.map → syntax-errors-D_VV5hAM.js.map} +1 -1
  59. package/dist/{typification-DDuv88ir.d.ts → typification-9lNu9R4k.d.ts} +8 -4
  60. package/dist/{typification-graph-8VdK6_Fh.d.ts → typification-graph-BY_oGT82.d.ts} +2 -2
  61. package/dist/{typification-parser-DHO42ZRc.d.ts → typification-parser-Dytsv9CI.d.ts} +2 -2
  62. package/dist/{value-api-C2X4Pom5.d.ts → value-api-CXLl_zg0.d.ts} +2 -2
  63. package/package.json +5 -1
  64. package/src/index.ts +8 -0
  65. package/src/library/diagnostics.ts +188 -0
  66. package/src/library/index.ts +9 -0
  67. package/src/library/oss-api.test.ts +1 -2
  68. package/src/library/rsform-api.ts +12 -17
  69. package/src/library/rsform.ts +4 -2
  70. package/src/library/rsmodel-api.ts +2 -8
  71. package/src/library/template-instantiation.test.ts +123 -5
  72. package/src/library/template-instantiation.ts +42 -12
  73. package/src/rslang/api.ts +2 -1
  74. package/src/rslang/labels.ts +10 -1
  75. package/src/rslang/semantic/type-auditor.test.ts +25 -6
  76. package/src/rslang/semantic/type-auditor.ts +2 -1
  77. package/src/rslang/semantic/typification-api.test.ts +21 -5
  78. package/src/rslang/semantic/typification-parser.test.ts +6 -1
  79. package/src/rslang/semantic/typification.ts +16 -6
  80. package/dist/library/rsform-api.js.map +0 -1
@@ -23,6 +23,7 @@ import {
23
23
  import { basic, bool, constant, type EchelonFunctional, isTypification } from '../rslang/semantic/typification';
24
24
  import { applyPath } from '../rslang/semantic/typification-api';
25
25
 
26
+ import { hasCstDiagnostic, RSDiagnosticCode } from './diagnostics';
26
27
  import { type LibraryItem } from './library';
27
28
  import {
28
29
  type ArgumentValue,
@@ -62,21 +63,9 @@ const CST_TYPE_TO_CLASS: Record<CstType, CstClass> = {
62
63
 
63
64
  /** Checks if {@link Constituenta} is a schema issue. */
64
65
  export function isSchemaIssue(cst: Constituenta): boolean {
65
- if (
66
- cst.homonyms.length > 0 ||
67
- cst.formalDuplicates.length > 0 ||
68
- cst.status === CstStatus.INCORRECT ||
69
- cst.status === CstStatus.INCALCULABLE ||
70
- cst.is_type_mismatch
71
- ) {
72
- return true;
73
- }
74
- if (isBasicConcept(cst.cst_type) && !isLogical(cst.cst_type)) {
75
- if (!cst.convention || !cst.term_resolved) {
76
- return true;
77
- }
78
- }
79
- return false;
66
+ return (
67
+ (cst.diagnostics?.length ?? 0) > 0 || cst.status === CstStatus.INCORRECT || cst.status === CstStatus.INCALCULABLE
68
+ );
80
69
  }
81
70
 
82
71
  /** Infers the status of an expression based on parsing and value information. */
@@ -489,8 +478,14 @@ export function calculateSchemaStats(target: RSForm): RSFormStats {
489
478
  step_complexity: items.reduce((sum, cst) => sum + calculateStepComplexity(cst), 0),
490
479
 
491
480
  count_problematic: items.reduce((sum, cst) => sum + (isSchemaIssue(cst) ? 1 : 0), 0),
492
- count_homonyms: items.reduce((sum, cst) => sum + (cst.homonyms.length > 0 ? 1 : 0), 0),
493
- count_formal_duplicates: items.reduce((sum, cst) => sum + (cst.formalDuplicates.length > 0 ? 1 : 0), 0),
481
+ count_homonyms: items.reduce(
482
+ (sum, cst) => sum + (hasCstDiagnostic(cst, RSDiagnosticCode.schemaHomonym) ? 1 : 0),
483
+ 0
484
+ ),
485
+ count_formal_duplicates: items.reduce(
486
+ (sum, cst) => sum + (hasCstDiagnostic(cst, RSDiagnosticCode.schemaFormalDuplicate) ? 1 : 0),
487
+ 0
488
+ ),
494
489
  count_missing_convention: items.reduce((sum, cst) => sum + (isMissingConvention(cst) ? 1 : 0), 0),
495
490
  count_type_mismatch: items.reduce((sum, cst) => sum + (cst.is_type_mismatch ? 1 : 0), 0),
496
491
 
@@ -5,6 +5,7 @@
5
5
  import { type Graph } from '../graph';
6
6
  import { type AnalysisBase, type ExpressionType, type RSLangAnalyzer, type TypePath } from '../rslang';
7
7
 
8
+ import { type CstDiagnostic } from './diagnostics';
8
9
  import { type CurrentVersion, type LibraryItem, type LibraryItemReference, type VersionInfo } from './library';
9
10
 
10
11
  /** Represents {@link Constituenta} type. */
@@ -93,10 +94,11 @@ export interface Constituenta {
93
94
  /** Controls whether base concepts expose property values or full values */
94
95
  value_is_property: boolean;
95
96
 
96
- homonyms: number[];
97
- formalDuplicates: number[];
98
97
  analysis: AnalysisBase;
99
98
 
99
+ /** Schema-kind {@link CstDiagnostic} list, populated during schema loading. */
100
+ diagnostics: CstDiagnostic[];
101
+
100
102
  /** Typification used for dependents, model values, and display (manual when it overrides computed). */
101
103
  effectiveType: ExpressionType | null;
102
104
 
@@ -3,6 +3,7 @@ import { compare, tuple, TUPLE_ID, VALUE_TRUE, type ValuePath } from '../rslang/
3
3
  import { extractValue, isTupleValue, makeDefaultValue, setNestedValue } from '../rslang/eval/value-api';
4
4
  import { type EchelonCollection } from '../rslang/semantic/typification';
5
5
 
6
+ import { modelStatusCstDiagnostic } from './diagnostics';
6
7
  import { type RSEngine } from './rsengine';
7
8
  import { type Constituenta, CstType, type RSForm } from './rsform';
8
9
  import { calculateSchemaStats, isBaseSet, isBasicConcept } from './rsform-api';
@@ -125,14 +126,7 @@ export function calculateModelStats(schema: RSForm, engine: RSEngine, _engineGen
125
126
 
126
127
  /** Checks whether evaluation status contributes to model status issues. */
127
128
  export function isModelIssue(engine: RSEngine, cst: Constituenta): boolean {
128
- const status = engine.getCstStatus(cst.id);
129
- const isBasic = isBasicConcept(cst.cst_type);
130
- return (
131
- status === EvalStatus.EVAL_FAIL ||
132
- status === EvalStatus.AXIOM_FALSE ||
133
- status === EvalStatus.INVALID_DATA ||
134
- (isBasic && status === EvalStatus.EMPTY)
135
- );
129
+ return modelStatusCstDiagnostic(engine.getCstStatus(cst.id), cst.cst_type) !== null;
136
130
  }
137
131
 
138
132
  /** Evaluate if {@link CstType} is interpretable. */
@@ -19,8 +19,7 @@ function mockCst(partial: Pick<Constituenta, 'id' | 'alias' | 'cst_type'> & Part
19
19
  term_forms: [],
20
20
  typification_manual: '',
21
21
  value_is_property: false,
22
- homonyms: [],
23
- formalDuplicates: [],
22
+ diagnostics: [],
24
23
  analysis: { success: true } as Constituenta['analysis'],
25
24
  effectiveType: null,
26
25
  is_type_mismatch: false,
@@ -219,17 +218,82 @@ describe('planTemplateInstantiation', () => {
219
218
  expect(plan.items[0].definition_formal).not.toContain('F42[');
220
219
  });
221
220
 
222
- it('skips template constituents that already exist in the target schema', () => {
221
+ it('instantiates bank dependency when target alias collides but definition differs', () => {
222
+ const schemaWithUnrelatedF42 = mockSchema([
223
+ ...targetSchema.items,
224
+ mockCst({
225
+ id: 12,
226
+ alias: 'F42',
227
+ cst_type: CstType.FUNCTION,
228
+ definition_formal: '[α∈R1] α'
229
+ })
230
+ ]);
231
+
232
+ const plan = planTemplateInstantiation({
233
+ targetSchema: schemaWithUnrelatedF42,
234
+ templateItems,
235
+ prototype: p30,
236
+ userArgs: [{ alias: 'σ', typification: 'ℬ((R1×R1)×R1)', value: 'S2' }],
237
+ mainItem: {
238
+ alias: 'A2',
239
+ cst_type: CstType.PREDICATE,
240
+ crucial: false,
241
+ convention: '',
242
+ definition_formal: p30.definition_formal,
243
+ definition_raw: '',
244
+ term_raw: p30.term_raw,
245
+ term_forms: [],
246
+ typification_manual: '',
247
+ value_is_property: false
248
+ }
249
+ });
250
+
251
+ const dependency = plan.items[0];
252
+ expect(plan.items).toHaveLength(2);
253
+ expect(dependency.alias).not.toBe('F42');
254
+ expect(dependency.definition_formal).toContain('S2');
255
+ expect(plan.items.at(-1)!.definition_formal).toContain(`${dependency.alias}[`);
256
+ expect(plan.items.at(-1)!.definition_formal).not.toContain('F42[');
257
+ });
258
+
259
+ it('reuses template dependency when target has matching substituted definition', () => {
260
+ const userArgs: ArgumentValue[] = [{ alias: 'σ', typification: 'ℬ((R1×R1)×R1)', value: 'S2' }];
261
+
262
+ const baseline = planTemplateInstantiation({
263
+ targetSchema,
264
+ templateItems,
265
+ prototype: p30,
266
+ userArgs,
267
+ mainItem: {
268
+ alias: 'A2',
269
+ cst_type: CstType.PREDICATE,
270
+ crucial: false,
271
+ convention: '',
272
+ definition_formal: p30.definition_formal,
273
+ definition_raw: '',
274
+ term_raw: p30.term_raw,
275
+ term_forms: [],
276
+ typification_manual: '',
277
+ value_is_property: false
278
+ }
279
+ });
280
+
281
+ const equivalentFunction = baseline.items[0];
223
282
  const schemaWithF42 = mockSchema([
224
283
  ...targetSchema.items,
225
- mockCst({ id: 12, alias: 'F42', cst_type: CstType.FUNCTION, definition_formal: f42.definition_formal })
284
+ mockCst({
285
+ id: 12,
286
+ alias: 'F42',
287
+ cst_type: equivalentFunction.cst_type,
288
+ definition_formal: equivalentFunction.definition_formal
289
+ })
226
290
  ]);
227
291
 
228
292
  const plan = planTemplateInstantiation({
229
293
  targetSchema: schemaWithF42,
230
294
  templateItems,
231
295
  prototype: p30,
232
- userArgs: [{ alias: 'σ', typification: 'ℬ((R1×R1)×R1)', value: 'S2' }],
296
+ userArgs,
233
297
  mainItem: {
234
298
  alias: 'A2',
235
299
  cst_type: CstType.PREDICATE,
@@ -305,4 +369,58 @@ describe('planTemplateInstantiation', () => {
305
369
  expect(plan.mainDuplicateAlias).toBe('A9');
306
370
  expect(plan.items.some(item => item.term_raw === p35.term_raw)).toBe(false);
307
371
  });
372
+
373
+ it('reports binding conflict when the same template parameter is bound to different aliases', () => {
374
+ const f10 = mockCst({
375
+ id: 5,
376
+ alias: 'F10',
377
+ cst_type: CstType.FUNCTION,
378
+ term_raw: 'g',
379
+ definition_formal: '[x∈R1] x'
380
+ });
381
+ const p11 = mockCst({
382
+ id: 6,
383
+ alias: 'P11',
384
+ cst_type: CstType.PREDICATE,
385
+ term_raw: 'h',
386
+ definition_formal: '[y∈R1] F10[y]'
387
+ });
388
+ const mainTemplate = mockCst({
389
+ id: 7,
390
+ alias: 'P12',
391
+ cst_type: CstType.PREDICATE,
392
+ term_raw: 'main',
393
+ definition_formal: '[a∈R1, b∈R1] P11[a] & P11[b]'
394
+ });
395
+
396
+ const plan = planTemplateInstantiation({
397
+ targetSchema,
398
+ templateItems: [f10, p11],
399
+ prototype: mainTemplate,
400
+ userArgs: [
401
+ { alias: 'a', typification: 'R1', value: 'S1' },
402
+ { alias: 'b', typification: 'R1', value: 'S2' }
403
+ ],
404
+ mainItem: {
405
+ alias: 'A1',
406
+ cst_type: CstType.PREDICATE,
407
+ crucial: false,
408
+ convention: '',
409
+ definition_formal: mainTemplate.definition_formal,
410
+ definition_raw: '',
411
+ term_raw: mainTemplate.term_raw,
412
+ term_forms: [],
413
+ typification_manual: '',
414
+ value_is_property: false
415
+ }
416
+ });
417
+
418
+ expect(plan.bindingConflict).toEqual({
419
+ templateAlias: 'P11',
420
+ paramAlias: 'y',
421
+ existing: 'S1',
422
+ incoming: 'S2'
423
+ });
424
+ expect(plan.items).toHaveLength(0);
425
+ });
308
426
  });
@@ -47,11 +47,19 @@ export interface TemplateInstantiationItem {
47
47
  value_is_property: boolean;
48
48
  }
49
49
 
50
+ /** Conflicting argument bindings inferred for the same template dependency. */
51
+ export interface TemplateBindingConflict {
52
+ templateAlias: string;
53
+ paramAlias: string;
54
+ existing: string;
55
+ incoming: string;
56
+ }
57
+
50
58
  /**
51
59
  * Batch create plan: dependencies first, selected template constituent last.
52
60
  *
53
- * Skips bank functions/predicates whose alias already exists in the target schema, and
54
- * constituents whose substituted formal definition already exists (normalized comparison).
61
+ * Reuses constituents whose substituted formal definition already exists in the target schema
62
+ * (normalized comparison), even when the bank alias differs from the matched target alias.
55
63
  * {@link TemplateInstantiationPlan.mainDuplicateAlias} reports when the selected template is a duplicate.
56
64
  * Does not set `insert_after`; the caller supplies insertion on the batch DTO.
57
65
  */
@@ -59,6 +67,8 @@ export interface TemplateInstantiationPlan {
59
67
  items: TemplateInstantiationItem[];
60
68
  /** Set when the selected template expression already exists in the target schema. */
61
69
  mainDuplicateAlias: string | null;
70
+ /** Set when nested template calls bind the same parameter to different global aliases. */
71
+ bindingConflict: TemplateBindingConflict | null;
62
72
  }
63
73
 
64
74
  /**
@@ -115,6 +125,7 @@ export class TemplateInstantiationPlanner {
115
125
  private readonly userMapping: AliasMapping;
116
126
  private readonly depBindings = new Map<string, Map<string, string>>();
117
127
  private readonly depRefs = new Map<string, Set<string>>();
128
+ private bindingConflict: TemplateBindingConflict | null = null;
118
129
 
119
130
  /** @param input - See {@link TemplateInstantiationInput}. */
120
131
  constructor(input: TemplateInstantiationInput) {
@@ -133,6 +144,10 @@ export class TemplateInstantiationPlanner {
133
144
  const mainDefinition = substituteTemplateArgs(this.prototype.definition_formal, this.userMapping);
134
145
  this.collectDependencies(mainDefinition, this.userMapping);
135
146
 
147
+ if (this.bindingConflict) {
148
+ return { items: [], mainDuplicateAlias: null, bindingConflict: this.bindingConflict };
149
+ }
150
+
136
151
  const definitionIndex = createFormalDefinitionIndex(this.targetSchema);
137
152
  const dependencyItems = this.buildDependencyItems(definitionIndex);
138
153
  const { item: mainItem, duplicateAlias: mainDuplicateAlias } = this.buildMainItem(
@@ -144,7 +159,8 @@ export class TemplateInstantiationPlanner {
144
159
 
145
160
  return {
146
161
  items: mainItem ? [...dependencyItems.items, mainItem] : dependencyItems.items,
147
- mainDuplicateAlias
162
+ mainDuplicateAlias,
163
+ bindingConflict: null
148
164
  };
149
165
  }
150
166
 
@@ -221,7 +237,10 @@ export class TemplateInstantiationPlanner {
221
237
  }
222
238
 
223
239
  private ensureDep(templateAlias: string, bindings: Map<string, string>, referrer?: string): void {
224
- if (targetHasAlias(this.targetSchema, templateAlias) || !this.templateByAlias.has(templateAlias)) {
240
+ if (this.bindingConflict) {
241
+ return;
242
+ }
243
+ if (!this.templateByAlias.has(templateAlias)) {
225
244
  return;
226
245
  }
227
246
  if (referrer && this.depBindings.has(referrer)) {
@@ -229,7 +248,12 @@ export class TemplateInstantiationPlanner {
229
248
  refs.add(templateAlias);
230
249
  this.depRefs.set(referrer, refs);
231
250
  }
232
- const merged = mergeBindings(this.depBindings.get(templateAlias), bindings);
251
+ const mergeResult = mergeBindings(this.depBindings.get(templateAlias), bindings);
252
+ if (!mergeResult.ok) {
253
+ this.bindingConflict = { templateAlias, ...mergeResult.conflict };
254
+ return;
255
+ }
256
+ const merged = mergeResult.bindings;
233
257
  const changed =
234
258
  !this.depBindings.has(templateAlias) || merged.size !== (this.depBindings.get(templateAlias)?.size ?? 0);
235
259
  this.depBindings.set(templateAlias, merged);
@@ -243,6 +267,9 @@ export class TemplateInstantiationPlanner {
243
267
  }
244
268
 
245
269
  private collectDependencies(expression: string, scope: AliasMapping, referrer?: string): void {
270
+ if (this.bindingConflict) {
271
+ return;
272
+ }
246
273
  const ast = parseRSLangExpression(expression);
247
274
  if (!ast) {
248
275
  return;
@@ -332,14 +359,21 @@ function resolveArgBinding(argExpr: string, scope: AliasMapping): string | undef
332
359
  return undefined;
333
360
  }
334
361
 
335
- function mergeBindings(existing: Map<string, string> | undefined, incoming: Map<string, string>): Map<string, string> {
362
+ type MergeBindingsResult =
363
+ | { ok: true; bindings: Map<string, string> }
364
+ | { ok: false; conflict: { paramAlias: string; existing: string; incoming: string } };
365
+
366
+ function mergeBindings(existing: Map<string, string> | undefined, incoming: Map<string, string>): MergeBindingsResult {
336
367
  const result = new Map(existing);
337
368
  for (const [alias, value] of incoming) {
338
- if (!result.has(alias)) {
369
+ const existingValue = result.get(alias);
370
+ if (existingValue === undefined) {
339
371
  result.set(alias, value);
372
+ } else if (existingValue !== value) {
373
+ return { ok: false, conflict: { paramAlias: alias, existing: existingValue, incoming: value } };
340
374
  }
341
375
  }
342
- return result;
376
+ return { ok: true, bindings: result };
343
377
  }
344
378
 
345
379
  function bindingMapToArgs(params: TemplateParam[], bindings: Map<string, string>): ArgumentValue[] {
@@ -401,10 +435,6 @@ function extractFuncCalls(ast: AstNode, expression: string): FuncCallRef[] {
401
435
  return result;
402
436
  }
403
437
 
404
- function targetHasAlias(schema: RSForm, alias: string): boolean {
405
- return schema.items.some(cst => cst.alias === alias);
406
- }
407
-
408
438
  function topologicalSort(aliases: string[], refs: Map<string, Set<string>>): string[] {
409
439
  const remaining = new Set(aliases);
410
440
  const sorted: string[] = [];
package/src/rslang/api.ts CHANGED
@@ -15,13 +15,14 @@ export type AliasMapping = Record<string, string>;
15
15
  const LOCALS_REGEXP = /[_a-zα-ω][a-zα-ω]*\d*/g;
16
16
  const GLOBAL_ALIAS_SOURCE = '[XCSADFPTN]\\d+';
17
17
  const GLOBALS_REGEXP = new RegExp(GLOBAL_ALIAS_SOURCE, 'g');
18
+ const GLOBAL_ALIAS_ANCHORED_REGEXP = new RegExp(`^${GLOBAL_ALIAS_SOURCE}$`);
18
19
  const COMPLEX_SYMBOLS_REGEXP = /[∀∃×ℬ;|:]/g;
19
20
  const TYPIFICATION_SET = /^ℬ+\([ℬ\(((X|C)\d+|Z)\)×]*\)$/g;
20
21
  // cspell:enable
21
22
 
22
23
  /** Whether {@link value} is a single schema global alias (e.g. `X1`, `F42`). */
23
24
  export function isGlobalAlias(value: string): boolean {
24
- return new RegExp(`^${GLOBAL_ALIAS_SOURCE}$`).test(value);
25
+ return GLOBAL_ALIAS_ANCHORED_REGEXP.test(value);
25
26
  }
26
27
 
27
28
  /**
@@ -138,6 +138,15 @@ export function labelRSLangNode(node: AstNodeBase): string {
138
138
  return 'UNKNOWN NODE: ' + String(node.typeID);
139
139
  }
140
140
 
141
+ /** Formats a base-set alias for display (mangled radicals `func<R#>` → `<func>R#`). */
142
+ export function labelBaseId(baseID: string): string {
143
+ const match = /^([^<]+)<(R[1-9]\d*)>$/.exec(baseID);
144
+ if (match) {
145
+ return `<${match[1]}>${match[2]}`;
146
+ }
147
+ return baseID;
148
+ }
149
+
141
150
  /** Converts expression type to string. */
142
151
  export function labelType(type: ExpressionType | null): string {
143
152
  if (!type) {
@@ -149,7 +158,7 @@ export function labelType(type: ExpressionType | null): string {
149
158
  case TypeID.integer:
150
159
  return INTEGER_TYPE_NAME;
151
160
  case TypeID.basic:
152
- return type.baseID;
161
+ return labelBaseId(type.baseID);
153
162
  case TypeID.tuple:
154
163
  return type.factors
155
164
  .map(factor => (factor.typeID === TypeID.tuple ? `(${labelType(factor)})` : labelType(factor)))
@@ -159,12 +159,12 @@ const errorData = [
159
159
  // Functions
160
160
  ['F1[X1]', { code: RSErrorCode.invalidArgsArity, from: 0, to: 6, params: ['2', '1'] }],
161
161
  ['F1[X1, X1, X1]', { code: RSErrorCode.invalidArgsArity, from: 0, to: 14, params: ['2', '3'] }],
162
- ['F1[X1, S1]', { code: RSErrorCode.invalidArgumentType, from: 7, to: 9, params: ['b∈ℬ(X1)', 'ℬ(X1×X1)'] }],
163
- ['F1[S1, X1]', { code: RSErrorCode.invalidArgumentType, from: 3, to: 5, params: ['a∈ℬ(X1)', 'ℬ(X1×X1)'] }],
162
+ ['F1[X1, S1]', { code: RSErrorCode.invalidArgumentType, from: 7, to: 9, params: ['F1<b>∈ℬ(X1)', 'ℬ(X1×X1)'] }],
163
+ ['F1[S1, X1]', { code: RSErrorCode.invalidArgumentType, from: 3, to: 5, params: ['F1<a>∈ℬ(X1)', 'ℬ(X1×X1)'] }],
164
164
  ['P1[X1]', { code: RSErrorCode.invalidArgsArity, from: 0, to: 6, params: ['2', '1'] }],
165
165
  ['P1[X1, X1, X1]', { code: RSErrorCode.invalidArgsArity, from: 0, to: 14, params: ['2', '3'] }],
166
- ['P1[X1, S1]', { code: RSErrorCode.invalidArgumentType, from: 7, to: 9, params: ['b∈ℬ(X1)', 'ℬ(X1×X1)'] }],
167
- ['P1[S1, X1]', { code: RSErrorCode.invalidArgumentType, from: 3, to: 5, params: ['a∈ℬ(X1)', 'ℬ(X1×X1)'] }],
166
+ ['P1[X1, S1]', { code: RSErrorCode.invalidArgumentType, from: 7, to: 9, params: ['P1<b>∈ℬ(X1)', 'ℬ(X1×X1)'] }],
167
+ ['P1[S1, X1]', { code: RSErrorCode.invalidArgumentType, from: 3, to: 5, params: ['P1<a>∈ℬ(X1)', 'ℬ(X1×X1)'] }],
168
168
  // Integral
169
169
  ['card(debool(X1))', { code: RSErrorCode.invalidCard, from: 5, to: 15, params: ['X1'] }],
170
170
  ['card(S3)', { code: RSErrorCode.invalidCard, from: 5, to: 7, params: ['C1'] }],
@@ -313,7 +313,26 @@ describe('TypeAuditor', () => {
313
313
  code: RSErrorCode.invalidArgumentType,
314
314
  from: 7,
315
315
  to: 12,
316
- params: ['b∈X1', 'ℬℬ(X1)']
316
+ params: ['F2<b>∈X1', 'ℬℬ(X1)']
317
+ });
318
+ });
319
+
320
+ it('Qualifies unresolved radicals and parameter aliases in invalidArgumentType message', () => {
321
+ typeContext.set('P44', {
322
+ typeID: TypeID.predicate,
323
+ result: LogicT,
324
+ args: [
325
+ { alias: 'α', type: bool(tuple([tuple([basic('R1'), basic('R2')]), basic('C1')])) },
326
+ { alias: 'β', type: bool(tuple([tuple([basic('R2'), basic('R3')]), basic('C1')])) }
327
+ ]
328
+ });
329
+ const inner = tuple([basic('X1'), basic('X2')]);
330
+ typeContext.set('S9', bool(tuple([inner, basic('C1')])));
331
+ expectError('P44[S9, S9]', {
332
+ code: RSErrorCode.invalidArgumentType,
333
+ from: 8,
334
+ to: 10,
335
+ params: ['P44<β>∈ℬ((X2×<P44>R3)×C1)', 'ℬ((X1×X2)×C1)']
317
336
  });
318
337
  });
319
338
 
@@ -331,7 +350,7 @@ describe('TypeAuditor', () => {
331
350
  code: RSErrorCode.invalidArgumentType,
332
351
  from: 7,
333
352
  to: 9,
334
- params: ['ζ∈ℬℬ(X1)', 'Z']
353
+ params: ['P2<ζ>∈ℬℬ(X1)', 'Z']
335
354
  });
336
355
  });
337
356
 
@@ -20,6 +20,7 @@ import {
20
20
  isTemplateRadical,
21
21
  isTypification,
22
22
  LogicT,
23
+ mangleParamAlias,
23
24
  mangleRadicalId,
24
25
  type Parametrized,
25
26
  tuple,
@@ -1025,7 +1026,7 @@ export class TypeAuditor {
1025
1026
  substituteBase(expectedType, substitutes);
1026
1027
  }
1027
1028
  this.onError(RSErrorCode.invalidArgumentType, node.children[child], [
1028
- `${type.args[child - 1].alias}∈${labelType(expectedType)}`,
1029
+ `${mangleParamAlias(type.args[child - 1].alias, alias)}∈${labelType(expectedType)}`,
1029
1030
  labelType(childType)
1030
1031
  ]);
1031
1032
  return null;
@@ -1,7 +1,16 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
3
  import type { TypePath } from './typification';
4
- import { AnyTypificationT, basic, bool, IntegerT, mangleRadicalId, tuple, type Typification } from './typification';
4
+ import {
5
+ AnyTypificationT,
6
+ basic,
7
+ bool,
8
+ IntegerT,
9
+ mangleParamAlias,
10
+ mangleRadicalId,
11
+ tuple,
12
+ type Typification
13
+ } from './typification';
5
14
  import { applyPath, cloneTypification, compareTemplated, substituteBase } from './typification-api';
6
15
 
7
16
  describe('applyPath', () => {
@@ -56,15 +65,22 @@ describe('compareTemplated', () => {
56
65
 
57
66
  describe('mangleRadicalId', () => {
58
67
  it('qualifies template radical with function name', () => {
59
- expect(mangleRadicalId('R1', 'P2')).toBe('R1<P2>');
60
- expect(mangleRadicalId('R2', 'F10')).toBe('R2<F10>');
68
+ expect(mangleRadicalId('R1', 'P2')).toBe('P2<R1>');
69
+ expect(mangleRadicalId('R2', 'F10')).toBe('F10<R2>');
70
+ });
71
+ });
72
+
73
+ describe('mangleParamAlias', () => {
74
+ it('qualifies formal parameter alias with calling function name', () => {
75
+ expect(mangleParamAlias('β', 'P44')).toBe('P44<β>');
76
+ expect(mangleParamAlias('a', 'F1')).toBe('F1<a>');
61
77
  });
62
78
  });
63
79
 
64
80
  describe('cloneTypification + substituteBase', () => {
65
81
  it('resolves mangled radicals for error messages', () => {
66
- const argType = bool(basic('R1<F2>'));
67
- const substitutes = new Map<string, Typification>([['R1<F2>', basic('X1')]]);
82
+ const argType = bool(basic('F2<R1>'));
83
+ const substitutes = new Map<string, Typification>([['F2<R1>', basic('X1')]]);
68
84
  const expectedType = cloneTypification(argType);
69
85
  substituteBase(expectedType, substitutes);
70
86
  expect(expectedType).toEqual(bool(basic('X1')));
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- import { labelType } from '../labels';
3
+ import { labelBaseId, labelType } from '../labels';
4
4
 
5
5
  import { TypeID } from './typification';
6
6
  import { applyAsciiTypeSubstitutions, parseTypeText } from './typification-parser';
@@ -47,4 +47,9 @@ describe('parseTypeText', () => {
47
47
  expect(applyAsciiTypeSubstitutions('X1*X2 -> X3')).toBe('X1×X2 → X3');
48
48
  expect(applyAsciiTypeSubstitutions('B(X1)')).toBe('ℬ(X1)');
49
49
  });
50
+
51
+ it('displays mangled radicals with function qualifier before radical', () => {
52
+ expect(labelBaseId('P44<R3>')).toBe('<P44>R3');
53
+ expect(labelType({ typeID: TypeID.basic, baseID: 'P2<R1>' })).toBe('<P2>R1');
54
+ });
50
55
  });
@@ -174,17 +174,27 @@ export function isTypification(type: ExpressionType | null): boolean {
174
174
  );
175
175
  }
176
176
 
177
- /** Checks if given alias is a template or mangled radical. */
178
- export function isRadical(alias: string): boolean {
179
- return /^R[1-9]\d*(?:<[^>]+>)?$/.test(alias);
180
- }
181
-
182
177
  /** Checks if given alias is an unmangled template radical. */
183
178
  export function isTemplateRadical(alias: string): boolean {
184
179
  return /^R[1-9]\d*$/.test(alias);
185
180
  }
186
181
 
182
+ /** Checks if given alias is a mangled template radical (`funcName<R#>`). */
183
+ export function isMangledRadical(alias: string): boolean {
184
+ return /^[^<]+<R[1-9]\d*>$/.test(alias);
185
+ }
186
+
187
+ /** Checks if given alias is a template or mangled radical. */
188
+ export function isRadical(alias: string): boolean {
189
+ return isTemplateRadical(alias) || isMangledRadical(alias);
190
+ }
191
+
187
192
  /** Mangle template radical with calling function name. */
188
193
  export function mangleRadicalId(radical: string, funcName: string): string {
189
- return `${radical}<${funcName}>`;
194
+ return `${funcName}<${radical}>`;
195
+ }
196
+
197
+ /** Mangle formal parameter alias with calling function name (for diagnostics). */
198
+ export function mangleParamAlias(alias: string, funcName: string): string {
199
+ return `${funcName}<${alias}>`;
190
200
  }