@manifesto-ai/compiler 1.8.0 → 1.8.2

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.
@@ -2002,16 +2002,16 @@ function inferExprType(expr, env, symbols) {
2002
2002
  location: expr.location
2003
2003
  };
2004
2004
  case "arrayLiteral": {
2005
- if (expr.elements.length === 0) {
2006
- return null;
2007
- }
2008
- const firstElementType = inferExprType(expr.elements[0], env, symbols);
2009
- if (!firstElementType) {
2005
+ const elementType = joinTypeCandidates(
2006
+ expr.elements.map((element) => inferExprType(element, env, symbols)),
2007
+ expr.location
2008
+ );
2009
+ if (!elementType) {
2010
2010
  return null;
2011
2011
  }
2012
2012
  return {
2013
2013
  kind: "arrayType",
2014
- elementType: firstElementType,
2014
+ elementType,
2015
2015
  location: expr.location
2016
2016
  };
2017
2017
  }
@@ -2030,8 +2030,9 @@ function inferExprType(expr, env, symbols) {
2030
2030
  case "functionCall":
2031
2031
  return inferFunctionCallType(expr, env, symbols);
2032
2032
  case "systemIdent":
2033
- case "iterationVar":
2034
2033
  return null;
2034
+ case "iterationVar":
2035
+ return env.get("$item") ?? null;
2035
2036
  }
2036
2037
  }
2037
2038
  function classifyComparableExpr(expr, env, symbols) {
@@ -2265,6 +2266,29 @@ function inferFunctionCallType(expr, env, symbols) {
2265
2266
  }
2266
2267
  return joinTypeCandidates([elementType, simpleType("null", expr.location)], expr.location);
2267
2268
  }
2269
+ if (expr.name === "filter" && expr.args.length >= 1) {
2270
+ return inferExprType(expr.args[0], env, symbols);
2271
+ }
2272
+ if (expr.name === "map" && expr.args.length >= 2) {
2273
+ const arrayType = inferExprType(expr.args[0], env, symbols);
2274
+ const elementType = getArrayElementType(arrayType, symbols);
2275
+ if (!elementType) {
2276
+ return null;
2277
+ }
2278
+ const mapperType = inferExprType(
2279
+ expr.args[1],
2280
+ extendCollectionEnv(env, elementType),
2281
+ symbols
2282
+ );
2283
+ if (!mapperType) {
2284
+ return null;
2285
+ }
2286
+ return {
2287
+ kind: "arrayType",
2288
+ elementType: mapperType,
2289
+ location: expr.location
2290
+ };
2291
+ }
2268
2292
  if ((expr.name === "first" || expr.name === "last") && expr.args.length >= 1) {
2269
2293
  return getArrayElementType(inferExprType(expr.args[0], env, symbols), symbols);
2270
2294
  }
@@ -2299,6 +2323,20 @@ function inferFunctionCallType(expr, env, symbols) {
2299
2323
  if (expr.name === "slice" && expr.args.length >= 1) {
2300
2324
  return inferExprType(expr.args[0], env, symbols);
2301
2325
  }
2326
+ if (expr.name === "split") {
2327
+ return {
2328
+ kind: "arrayType",
2329
+ elementType: simpleType("string", expr.location),
2330
+ location: expr.location
2331
+ };
2332
+ }
2333
+ if (expr.name === "keys") {
2334
+ return {
2335
+ kind: "arrayType",
2336
+ elementType: simpleType("string", expr.location),
2337
+ location: expr.location
2338
+ };
2339
+ }
2302
2340
  return null;
2303
2341
  }
2304
2342
  function joinComparableClasses(classes) {
@@ -2360,6 +2398,11 @@ function getStaticPropertyName(expr) {
2360
2398
  }
2361
2399
  return null;
2362
2400
  }
2401
+ function extendCollectionEnv(env, itemType) {
2402
+ const next = new Map(env);
2403
+ next.set("$item", itemType);
2404
+ return next;
2405
+ }
2363
2406
 
2364
2407
  // src/analyzer/entity-primitives.ts
2365
2408
  var ENTITY_LOOKUP_FNS = /* @__PURE__ */ new Set(["findById", "existsById"]);
@@ -2680,6 +2723,288 @@ function createContext() {
2680
2723
  };
2681
2724
  }
2682
2725
  var STOP_WAITING_REASON_PATTERN = /\b(await(?:ing)?|wait(?:ing)?|pending)\b/i;
2726
+ function simpleTypeNode(name, location) {
2727
+ return {
2728
+ kind: "simpleType",
2729
+ name,
2730
+ location
2731
+ };
2732
+ }
2733
+ function isAssignableType(sourceType, targetType, symbols) {
2734
+ const resolvedSource = resolveType(sourceType, symbols);
2735
+ const resolvedTarget = resolveType(targetType, symbols);
2736
+ if (!resolvedSource || !resolvedTarget) {
2737
+ return null;
2738
+ }
2739
+ if (resolvedTarget.kind === "unionType") {
2740
+ const sourceMembers = resolvedSource.kind === "unionType" ? resolvedSource.types : [resolvedSource];
2741
+ let sawUnknown = false;
2742
+ for (const member of sourceMembers) {
2743
+ const outcomes = resolvedTarget.types.map(
2744
+ (candidate) => isAssignableType(member, candidate, symbols)
2745
+ );
2746
+ if (outcomes.includes(true)) {
2747
+ continue;
2748
+ }
2749
+ if (outcomes.every((outcome) => outcome === false)) {
2750
+ return false;
2751
+ }
2752
+ sawUnknown = true;
2753
+ }
2754
+ return sawUnknown ? null : true;
2755
+ }
2756
+ if (resolvedSource.kind === "unionType") {
2757
+ let sawUnknown = false;
2758
+ for (const member of resolvedSource.types) {
2759
+ const outcome = isAssignableType(member, resolvedTarget, symbols);
2760
+ if (outcome === false) {
2761
+ return false;
2762
+ }
2763
+ if (outcome === null) {
2764
+ sawUnknown = true;
2765
+ }
2766
+ }
2767
+ return sawUnknown ? null : true;
2768
+ }
2769
+ if (resolvedTarget.kind === "simpleType") {
2770
+ if (resolvedSource.kind === "simpleType") {
2771
+ return resolvedSource.name === resolvedTarget.name;
2772
+ }
2773
+ if (resolvedSource.kind === "literalType") {
2774
+ if (resolvedTarget.name === "null") {
2775
+ return resolvedSource.value === null;
2776
+ }
2777
+ return typeof resolvedSource.value === resolvedTarget.name;
2778
+ }
2779
+ }
2780
+ if (resolvedTarget.kind === "literalType") {
2781
+ if (resolvedSource.kind !== "literalType") {
2782
+ return false;
2783
+ }
2784
+ return resolvedSource.value === resolvedTarget.value;
2785
+ }
2786
+ if (resolvedTarget.kind === "arrayType") {
2787
+ if (resolvedSource.kind !== "arrayType") {
2788
+ return false;
2789
+ }
2790
+ return isAssignableType(resolvedSource.elementType, resolvedTarget.elementType, symbols);
2791
+ }
2792
+ if (resolvedTarget.kind === "objectType") {
2793
+ if (resolvedSource.kind !== "objectType") {
2794
+ return false;
2795
+ }
2796
+ for (const targetField of resolvedTarget.fields) {
2797
+ const sourceField = resolvedSource.fields.find((candidate) => candidate.name === targetField.name);
2798
+ if (!sourceField) {
2799
+ if (targetField.optional) {
2800
+ continue;
2801
+ }
2802
+ return false;
2803
+ }
2804
+ const fieldAssignable = isAssignableType(sourceField.typeExpr, targetField.typeExpr, symbols);
2805
+ if (fieldAssignable !== true) {
2806
+ return fieldAssignable;
2807
+ }
2808
+ }
2809
+ return true;
2810
+ }
2811
+ if (resolvedTarget.kind === "recordType") {
2812
+ if (resolvedSource.kind !== "recordType") {
2813
+ return null;
2814
+ }
2815
+ return isAssignableType(resolvedSource.valueType, resolvedTarget.valueType, symbols);
2816
+ }
2817
+ return null;
2818
+ }
2819
+ function describeTypeExpr(typeExpr, symbols) {
2820
+ const resolved = resolveType(typeExpr, symbols);
2821
+ if (!resolved) {
2822
+ return "unknown";
2823
+ }
2824
+ switch (resolved.kind) {
2825
+ case "simpleType":
2826
+ return resolved.name;
2827
+ case "literalType":
2828
+ return JSON.stringify(resolved.value);
2829
+ case "arrayType":
2830
+ return `Array<${describeTypeExpr(resolved.elementType, symbols)}>`;
2831
+ case "recordType":
2832
+ return `Record<${describeTypeExpr(resolved.keyType, symbols)}, ${describeTypeExpr(resolved.valueType, symbols)}>`;
2833
+ case "objectType":
2834
+ return `{ ${resolved.fields.map((field) => `${field.name}${field.optional ? "?" : ""}: ${describeTypeExpr(field.typeExpr, symbols)}`).join("; ")} }`;
2835
+ case "unionType":
2836
+ return resolved.types.map((member) => describeTypeExpr(member, symbols)).join(" | ");
2837
+ }
2838
+ }
2839
+ function collectPrimitiveKinds(typeExpr, symbols) {
2840
+ const resolved = resolveType(typeExpr, symbols);
2841
+ if (!resolved) {
2842
+ return null;
2843
+ }
2844
+ switch (resolved.kind) {
2845
+ case "simpleType":
2846
+ if (resolved.name === "string" || resolved.name === "number" || resolved.name === "boolean" || resolved.name === "null") {
2847
+ return /* @__PURE__ */ new Set([resolved.name]);
2848
+ }
2849
+ return resolved.name === "object" ? "nonprimitive" : null;
2850
+ case "literalType":
2851
+ return /* @__PURE__ */ new Set([
2852
+ resolved.value === null ? "null" : typeof resolved.value
2853
+ ]);
2854
+ case "arrayType":
2855
+ case "recordType":
2856
+ case "objectType":
2857
+ return "nonprimitive";
2858
+ case "unionType": {
2859
+ const kinds = /* @__PURE__ */ new Set();
2860
+ for (const member of resolved.types) {
2861
+ const memberKinds = collectPrimitiveKinds(member, symbols);
2862
+ if (memberKinds === null) {
2863
+ return null;
2864
+ }
2865
+ if (memberKinds === "nonprimitive") {
2866
+ return "nonprimitive";
2867
+ }
2868
+ for (const kind of memberKinds) {
2869
+ kinds.add(kind);
2870
+ }
2871
+ }
2872
+ return kinds;
2873
+ }
2874
+ }
2875
+ }
2876
+ function areComparableTypesCompatible(leftType, rightType, symbols) {
2877
+ const leftKinds = collectPrimitiveKinds(leftType, symbols);
2878
+ const rightKinds = collectPrimitiveKinds(rightType, symbols);
2879
+ if (leftKinds === null || rightKinds === null) {
2880
+ return null;
2881
+ }
2882
+ if (leftKinds === "nonprimitive" || rightKinds === "nonprimitive") {
2883
+ return false;
2884
+ }
2885
+ if (!(leftKinds instanceof Set) || !(rightKinds instanceof Set)) {
2886
+ return null;
2887
+ }
2888
+ const leftNonNull = [...leftKinds].filter((kind) => kind !== "null");
2889
+ const rightNonNull = [...rightKinds].filter((kind) => kind !== "null");
2890
+ if (leftNonNull.length === 0 || rightNonNull.length === 0) {
2891
+ return true;
2892
+ }
2893
+ return leftNonNull.some((kind) => rightNonNull.includes(kind));
2894
+ }
2895
+ function stripNullType(typeExpr, symbols) {
2896
+ const resolved = resolveType(typeExpr, symbols);
2897
+ if (!resolved || isNullType(resolved)) {
2898
+ return null;
2899
+ }
2900
+ if (resolved.kind !== "unionType") {
2901
+ return resolved;
2902
+ }
2903
+ const members = resolved.types.filter((member) => !isNullType(member));
2904
+ if (members.length === 0) {
2905
+ return null;
2906
+ }
2907
+ if (members.length === 1) {
2908
+ return members[0];
2909
+ }
2910
+ return {
2911
+ kind: "unionType",
2912
+ types: members,
2913
+ location: resolved.location
2914
+ };
2915
+ }
2916
+ function areTypesCompatible(leftType, rightType, symbols) {
2917
+ if (!leftType || !rightType) {
2918
+ return null;
2919
+ }
2920
+ const leftToRight = isAssignableType(leftType, rightType, symbols);
2921
+ if (leftToRight === true) {
2922
+ return true;
2923
+ }
2924
+ const rightToLeft = isAssignableType(rightType, leftType, symbols);
2925
+ if (rightToLeft === true) {
2926
+ return true;
2927
+ }
2928
+ const comparable = areComparableTypesCompatible(leftType, rightType, symbols);
2929
+ if (comparable !== null) {
2930
+ return comparable;
2931
+ }
2932
+ if (leftToRight === false && rightToLeft === false) {
2933
+ return false;
2934
+ }
2935
+ return null;
2936
+ }
2937
+ function classifyArrayOperand(typeExpr, symbols) {
2938
+ const resolved = resolveType(typeExpr, symbols);
2939
+ if (!resolved) {
2940
+ return null;
2941
+ }
2942
+ if (resolved.kind === "unionType") {
2943
+ const outcomes = resolved.types.map((member) => classifyArrayOperand(member, symbols));
2944
+ if (outcomes.every((outcome) => outcome === true)) {
2945
+ return true;
2946
+ }
2947
+ return outcomes.some((outcome) => outcome === false) ? false : null;
2948
+ }
2949
+ return resolved.kind === "arrayType";
2950
+ }
2951
+ function classifyLenOperand(typeExpr, symbols) {
2952
+ const resolved = resolveType(typeExpr, symbols);
2953
+ if (!resolved) {
2954
+ return null;
2955
+ }
2956
+ if (resolved.kind === "unionType") {
2957
+ const outcomes = resolved.types.map((member) => classifyLenOperand(member, symbols));
2958
+ if (outcomes.every((outcome) => outcome === true)) {
2959
+ return true;
2960
+ }
2961
+ return outcomes.some((outcome) => outcome === false) ? false : null;
2962
+ }
2963
+ if (resolved.kind === "arrayType" || resolved.kind === "recordType" || resolved.kind === "objectType") {
2964
+ return true;
2965
+ }
2966
+ if (resolved.kind === "literalType") {
2967
+ return typeof resolved.value === "string";
2968
+ }
2969
+ if (resolved.kind === "simpleType") {
2970
+ return resolved.name === "string" || resolved.name === "object";
2971
+ }
2972
+ return false;
2973
+ }
2974
+ function extendCollectionTypeEnv(baseEnv, itemType) {
2975
+ const next = new Map(baseEnv);
2976
+ next.set("$item", itemType);
2977
+ return next;
2978
+ }
2979
+ function resolvePathType(path, symbols) {
2980
+ const [first, ...rest] = path.segments;
2981
+ if (!first || first.kind !== "propertySegment") {
2982
+ return null;
2983
+ }
2984
+ let current = symbols.stateTypes.get(first.name) ?? null;
2985
+ for (const segment of rest) {
2986
+ if (!current) {
2987
+ return null;
2988
+ }
2989
+ current = segment.kind === "propertySegment" ? getPropertyType(current, segment.name, symbols) : getIndexType(current, symbols);
2990
+ }
2991
+ return current;
2992
+ }
2993
+ function renderPath(path) {
2994
+ let result = "";
2995
+ for (const [index, segment] of path.segments.entries()) {
2996
+ if (segment.kind === "propertySegment") {
2997
+ result += index === 0 ? segment.name : `.${segment.name}`;
2998
+ continue;
2999
+ }
3000
+ if (segment.index.kind === "literal") {
3001
+ result += `[${JSON.stringify(segment.index.value)}]`;
3002
+ } else {
3003
+ result += "[*]";
3004
+ }
3005
+ }
3006
+ return result;
3007
+ }
2683
3008
  var SemanticValidator = class {
2684
3009
  ctx = createContext();
2685
3010
  symbols = null;
@@ -3063,7 +3388,23 @@ var SemanticValidator = class {
3063
3388
  );
3064
3389
  }
3065
3390
  if (stmt.value) {
3066
- this.validateExpr(stmt.value, "action");
3391
+ const before = this.ctx.diagnostics.length;
3392
+ const valueType = this.validateExpr(stmt.value, "action");
3393
+ if (!this.symbols || before !== this.ctx.diagnostics.length) {
3394
+ return;
3395
+ }
3396
+ const targetType = resolvePathType(stmt.path, this.symbols);
3397
+ if (!targetType || !valueType) {
3398
+ return;
3399
+ }
3400
+ const assignable = isAssignableType(valueType, targetType, this.symbols);
3401
+ if (assignable === false) {
3402
+ this.error(
3403
+ `Patch value for '${renderPath(stmt.path)}' must be assignable to ${describeTypeExpr(targetType, this.symbols)}, got ${describeTypeExpr(valueType, this.symbols)}`,
3404
+ stmt.value.location,
3405
+ "E_TYPE_MISMATCH"
3406
+ );
3407
+ }
3067
3408
  }
3068
3409
  }
3069
3410
  validateEffect(stmt) {
@@ -3109,57 +3450,144 @@ var SemanticValidator = class {
3109
3450
  }
3110
3451
  }
3111
3452
  validateCondition(expr, guardType) {
3112
- this.validateExpr(expr, "action");
3113
- if (expr.kind === "literal" && typeof expr.value !== "boolean") {
3114
- this.warn(
3115
- `Condition in ${guardType} is a non-boolean literal. Consider using a boolean expression`,
3453
+ const before = this.ctx.diagnostics.length;
3454
+ const conditionType = this.validateExpr(expr, "action");
3455
+ if (before === this.ctx.diagnostics.length) {
3456
+ this.requireAssignable(
3457
+ conditionType,
3458
+ simpleTypeNode("boolean", expr.location),
3116
3459
  expr.location,
3117
- "W_NON_BOOL_COND"
3460
+ `Condition in ${guardType} must evaluate to boolean`
3118
3461
  );
3119
3462
  }
3120
3463
  }
3121
- validateExpr(expr, context) {
3464
+ validateExpr(expr, context, env = this.ctx.currentActionParamTypes) {
3122
3465
  switch (expr.kind) {
3123
3466
  case "functionCall":
3124
- this.validateFunctionCall(expr, context);
3125
- break;
3126
- case "binary":
3127
- this.validateExpr(expr.left, context);
3128
- this.validateExpr(expr.right, context);
3129
- if (expr.operator === "==" || expr.operator === "!=") {
3130
- this.validatePrimitiveEquality(expr.left, expr.right, expr.location);
3467
+ this.validateFunctionCall(expr, context, env);
3468
+ return this.inferType(expr, env);
3469
+ case "binary": {
3470
+ const before = this.ctx.diagnostics.length;
3471
+ const leftType = this.validateExpr(expr.left, context, env);
3472
+ const rightType = this.validateExpr(expr.right, context, env);
3473
+ const hadInnerErrors = before !== this.ctx.diagnostics.length;
3474
+ if (!hadInnerErrors) {
3475
+ switch (expr.operator) {
3476
+ case "==":
3477
+ case "!=":
3478
+ this.validatePrimitiveEquality(expr.left, expr.right, leftType, rightType, expr.location);
3479
+ break;
3480
+ case "<":
3481
+ case "<=":
3482
+ case ">":
3483
+ case ">=":
3484
+ this.requireAssignable(
3485
+ leftType,
3486
+ simpleTypeNode("number", expr.left.location),
3487
+ expr.left.location,
3488
+ `Operator '${expr.operator}' requires a numeric left operand`
3489
+ );
3490
+ this.requireAssignable(
3491
+ rightType,
3492
+ simpleTypeNode("number", expr.right.location),
3493
+ expr.right.location,
3494
+ `Operator '${expr.operator}' requires a numeric right operand`
3495
+ );
3496
+ break;
3497
+ case "&&":
3498
+ case "||":
3499
+ this.requireAssignable(
3500
+ leftType,
3501
+ simpleTypeNode("boolean", expr.left.location),
3502
+ expr.left.location,
3503
+ `Operator '${expr.operator}' requires a boolean left operand`
3504
+ );
3505
+ this.requireAssignable(
3506
+ rightType,
3507
+ simpleTypeNode("boolean", expr.right.location),
3508
+ expr.right.location,
3509
+ `Operator '${expr.operator}' requires a boolean right operand`
3510
+ );
3511
+ break;
3512
+ case "+":
3513
+ case "-":
3514
+ case "*":
3515
+ case "/":
3516
+ case "%":
3517
+ this.requireAssignable(
3518
+ leftType,
3519
+ simpleTypeNode("number", expr.left.location),
3520
+ expr.left.location,
3521
+ `Operator '${expr.operator}' requires a numeric left operand`
3522
+ );
3523
+ this.requireAssignable(
3524
+ rightType,
3525
+ simpleTypeNode("number", expr.right.location),
3526
+ expr.right.location,
3527
+ `Operator '${expr.operator}' requires a numeric right operand`
3528
+ );
3529
+ break;
3530
+ case "??":
3531
+ this.validateCoalesceTypes([leftType, rightType], expr.location);
3532
+ break;
3533
+ }
3131
3534
  }
3132
- break;
3133
- case "unary":
3134
- this.validateExpr(expr.operand, context);
3135
- break;
3136
- case "ternary":
3137
- this.validateExpr(expr.condition, context);
3138
- this.validateExpr(expr.consequent, context);
3139
- this.validateExpr(expr.alternate, context);
3140
- break;
3535
+ return this.inferType(expr, env);
3536
+ }
3537
+ case "unary": {
3538
+ const before = this.ctx.diagnostics.length;
3539
+ const operandType = this.validateExpr(expr.operand, context, env);
3540
+ if (before === this.ctx.diagnostics.length) {
3541
+ this.requireAssignable(
3542
+ operandType,
3543
+ simpleTypeNode(expr.operator === "!" ? "boolean" : "number", expr.operand.location),
3544
+ expr.operand.location,
3545
+ expr.operator === "!" ? "Unary '!' requires a boolean operand" : "Unary '-' requires a numeric operand"
3546
+ );
3547
+ }
3548
+ return this.inferType(expr, env);
3549
+ }
3550
+ case "ternary": {
3551
+ const before = this.ctx.diagnostics.length;
3552
+ const conditionType = this.validateExpr(expr.condition, context, env);
3553
+ this.validateExpr(expr.consequent, context, env);
3554
+ this.validateExpr(expr.alternate, context, env);
3555
+ if (before === this.ctx.diagnostics.length) {
3556
+ this.requireAssignable(
3557
+ conditionType,
3558
+ simpleTypeNode("boolean", expr.condition.location),
3559
+ expr.condition.location,
3560
+ "Ternary condition must evaluate to boolean"
3561
+ );
3562
+ }
3563
+ return this.inferType(expr, env);
3564
+ }
3141
3565
  case "propertyAccess":
3142
- this.validateExpr(expr.object, context);
3143
- break;
3566
+ this.validateExpr(expr.object, context, env);
3567
+ return this.inferType(expr, env);
3144
3568
  case "indexAccess":
3145
- this.validateExpr(expr.object, context);
3146
- this.validateExpr(expr.index, context);
3147
- break;
3569
+ this.validateExpr(expr.object, context, env);
3570
+ this.validateExpr(expr.index, context, env);
3571
+ return this.inferType(expr, env);
3148
3572
  case "objectLiteral":
3149
3573
  for (const prop of expr.properties) {
3150
- this.validateExpr(prop.value, context);
3574
+ this.validateExpr(prop.value, context, env);
3151
3575
  }
3152
- break;
3576
+ return this.inferType(expr, env);
3153
3577
  case "arrayLiteral":
3154
3578
  for (const elem of expr.elements) {
3155
- this.validateExpr(elem, context);
3579
+ this.validateExpr(elem, context, env);
3156
3580
  }
3157
- break;
3581
+ return this.inferType(expr, env);
3158
3582
  case "systemIdent":
3159
- break;
3583
+ return this.inferType(expr, env);
3584
+ case "literal":
3585
+ case "identifier":
3586
+ case "iterationVar":
3587
+ return this.inferType(expr, env);
3160
3588
  }
3161
3589
  }
3162
- validateFunctionCall(expr, context) {
3590
+ validateFunctionCall(expr, context, env) {
3163
3591
  const { name, args, location } = expr;
3164
3592
  if (["reduce", "fold", "foldl", "foldr", "scan"].includes(name)) {
3165
3593
  this.error(
@@ -3189,9 +3617,6 @@ var SemanticValidator = class {
3189
3617
  // FDR-MEL-042: eq/neq on primitives only
3190
3618
  case "eq":
3191
3619
  case "neq":
3192
- if (args.length === 2) {
3193
- this.validatePrimitiveEquality(args[0], args[1], location);
3194
- }
3195
3620
  break;
3196
3621
  // FDR-MEL-026: len() on Array only
3197
3622
  case "len":
@@ -3362,32 +3787,318 @@ var SemanticValidator = class {
3362
3787
  );
3363
3788
  break;
3364
3789
  }
3365
- for (const arg of args) {
3366
- this.validateExpr(arg, context);
3790
+ const argTypes = [];
3791
+ if (["filter", "map", "find", "every", "some"].includes(name) && args.length > 0) {
3792
+ const sourceType = this.validateExpr(args[0], context, env);
3793
+ argTypes.push(sourceType);
3794
+ let callbackEnv = env;
3795
+ if (this.symbols) {
3796
+ const itemType = getArrayElementType(sourceType, this.symbols);
3797
+ if (itemType) {
3798
+ callbackEnv = extendCollectionTypeEnv(env, itemType);
3799
+ }
3800
+ }
3801
+ for (let index = 1; index < args.length; index += 1) {
3802
+ argTypes.push(this.validateExpr(args[index], context, index === 1 ? callbackEnv : env));
3803
+ }
3804
+ } else {
3805
+ for (const arg of args) {
3806
+ argTypes.push(this.validateExpr(arg, context, env));
3807
+ }
3808
+ }
3809
+ if (!this.symbols) {
3810
+ return;
3811
+ }
3812
+ switch (name) {
3813
+ case "eq":
3814
+ case "neq":
3815
+ if (args.length === 2) {
3816
+ this.validatePrimitiveEquality(args[0], args[1], argTypes[0], argTypes[1], location);
3817
+ }
3818
+ break;
3819
+ case "add":
3820
+ case "sub":
3821
+ case "mul":
3822
+ case "div":
3823
+ case "mod":
3824
+ case "pow":
3825
+ if (args.length === 2) {
3826
+ this.requireAssignable(
3827
+ argTypes[0],
3828
+ simpleTypeNode("number", args[0].location),
3829
+ args[0].location,
3830
+ `Function '${name}' expects a numeric first argument`
3831
+ );
3832
+ this.requireAssignable(
3833
+ argTypes[1],
3834
+ simpleTypeNode("number", args[1].location),
3835
+ args[1].location,
3836
+ `Function '${name}' expects a numeric second argument`
3837
+ );
3838
+ }
3839
+ break;
3840
+ case "gt":
3841
+ case "gte":
3842
+ case "lt":
3843
+ case "lte":
3844
+ if (args.length === 2) {
3845
+ this.requireAssignable(
3846
+ argTypes[0],
3847
+ simpleTypeNode("number", args[0].location),
3848
+ args[0].location,
3849
+ `Function '${name}' expects a numeric first argument`
3850
+ );
3851
+ this.requireAssignable(
3852
+ argTypes[1],
3853
+ simpleTypeNode("number", args[1].location),
3854
+ args[1].location,
3855
+ `Function '${name}' expects a numeric second argument`
3856
+ );
3857
+ }
3858
+ break;
3859
+ case "and":
3860
+ case "or":
3861
+ for (const [index, arg] of args.entries()) {
3862
+ this.requireAssignable(
3863
+ argTypes[index],
3864
+ simpleTypeNode("boolean", arg.location),
3865
+ arg.location,
3866
+ `Function '${name}' expects boolean arguments`
3867
+ );
3868
+ }
3869
+ break;
3870
+ case "not":
3871
+ if (args.length === 1) {
3872
+ this.requireAssignable(
3873
+ argTypes[0],
3874
+ simpleTypeNode("boolean", args[0].location),
3875
+ args[0].location,
3876
+ "Function 'not' expects a boolean argument"
3877
+ );
3878
+ }
3879
+ break;
3880
+ case "neg":
3881
+ case "abs":
3882
+ case "floor":
3883
+ case "ceil":
3884
+ case "round":
3885
+ case "sqrt":
3886
+ if (args.length === 1) {
3887
+ this.requireAssignable(
3888
+ argTypes[0],
3889
+ simpleTypeNode("number", args[0].location),
3890
+ args[0].location,
3891
+ `Function '${name}' expects a numeric argument`
3892
+ );
3893
+ }
3894
+ break;
3895
+ case "trim":
3896
+ case "lower":
3897
+ case "upper":
3898
+ case "strlen":
3899
+ if (args.length === 1) {
3900
+ this.requireAssignable(
3901
+ argTypes[0],
3902
+ simpleTypeNode("string", args[0].location),
3903
+ args[0].location,
3904
+ `Function '${name}' expects a string argument`
3905
+ );
3906
+ }
3907
+ break;
3908
+ case "startsWith":
3909
+ case "endsWith":
3910
+ case "strIncludes":
3911
+ case "indexOf":
3912
+ case "split":
3913
+ if (args.length === 2) {
3914
+ this.requireAssignable(
3915
+ argTypes[0],
3916
+ simpleTypeNode("string", args[0].location),
3917
+ args[0].location,
3918
+ `Function '${name}' expects a string first argument`
3919
+ );
3920
+ this.requireAssignable(
3921
+ argTypes[1],
3922
+ simpleTypeNode("string", args[1].location),
3923
+ args[1].location,
3924
+ `Function '${name}' expects a string second argument`
3925
+ );
3926
+ }
3927
+ break;
3928
+ case "replace":
3929
+ if (args.length >= 2) {
3930
+ this.requireAssignable(
3931
+ argTypes[0],
3932
+ simpleTypeNode("string", args[0].location),
3933
+ args[0].location,
3934
+ "Function 'replace' expects a string first argument"
3935
+ );
3936
+ this.requireAssignable(
3937
+ argTypes[1],
3938
+ simpleTypeNode("string", args[1].location),
3939
+ args[1].location,
3940
+ "Function 'replace' expects a string second argument"
3941
+ );
3942
+ }
3943
+ if (args.length === 3) {
3944
+ this.requireAssignable(
3945
+ argTypes[2],
3946
+ simpleTypeNode("string", args[2].location),
3947
+ args[2].location,
3948
+ "Function 'replace' expects a string replacement argument"
3949
+ );
3950
+ }
3951
+ break;
3952
+ case "substring":
3953
+ case "substr":
3954
+ if (args.length >= 2) {
3955
+ this.requireAssignable(
3956
+ argTypes[0],
3957
+ simpleTypeNode("string", args[0].location),
3958
+ args[0].location,
3959
+ `Function '${name}' expects a string first argument`
3960
+ );
3961
+ this.requireAssignable(
3962
+ argTypes[1],
3963
+ simpleTypeNode("number", args[1].location),
3964
+ args[1].location,
3965
+ `Function '${name}' expects a numeric second argument`
3966
+ );
3967
+ }
3968
+ if (args.length === 3) {
3969
+ this.requireAssignable(
3970
+ argTypes[2],
3971
+ simpleTypeNode("number", args[2].location),
3972
+ args[2].location,
3973
+ `Function '${name}' expects a numeric third argument`
3974
+ );
3975
+ }
3976
+ break;
3977
+ case "len":
3978
+ if (args.length === 1) {
3979
+ this.requireLenCompatible(argTypes[0], args[0].location);
3980
+ }
3981
+ break;
3982
+ case "filter":
3983
+ case "find":
3984
+ case "every":
3985
+ case "some":
3986
+ if (args.length === 2) {
3987
+ this.requireArrayCompatible(argTypes[0], args[0].location, name);
3988
+ this.requireAssignable(
3989
+ argTypes[1],
3990
+ simpleTypeNode("boolean", args[1].location),
3991
+ args[1].location,
3992
+ `Function '${name}' requires a boolean-valued callback`
3993
+ );
3994
+ }
3995
+ break;
3996
+ case "map":
3997
+ if (args.length === 2) {
3998
+ this.requireArrayCompatible(argTypes[0], args[0].location, name);
3999
+ }
4000
+ break;
4001
+ case "coalesce":
4002
+ this.validateCoalesceTypes(argTypes, location);
4003
+ break;
4004
+ case "if":
4005
+ case "cond":
4006
+ if (args.length === 3) {
4007
+ this.requireAssignable(
4008
+ argTypes[0],
4009
+ simpleTypeNode("boolean", args[0].location),
4010
+ args[0].location,
4011
+ `Function '${name}' expects a boolean condition`
4012
+ );
4013
+ }
4014
+ break;
4015
+ }
4016
+ }
4017
+ validatePrimitiveEquality(leftExpr, rightExpr, leftType, rightType, location) {
4018
+ if (!this.symbols) {
4019
+ return;
4020
+ }
4021
+ if (leftExpr.kind === "objectLiteral" || leftExpr.kind === "arrayLiteral" || rightExpr.kind === "objectLiteral" || rightExpr.kind === "arrayLiteral") {
4022
+ this.error(
4023
+ "eq/neq operands must be compatible primitive types, not object or array literals",
4024
+ location,
4025
+ "E_TYPE_MISMATCH"
4026
+ );
4027
+ return;
4028
+ }
4029
+ const compatible = areComparableTypesCompatible(leftType, rightType, this.symbols);
4030
+ if (compatible === false) {
4031
+ this.error(
4032
+ `eq/neq operands must be compatible primitive types, got ${describeTypeExpr(leftType, this.symbols)} and ${describeTypeExpr(rightType, this.symbols)}`,
4033
+ location,
4034
+ "E_TYPE_MISMATCH"
4035
+ );
3367
4036
  }
3368
4037
  }
3369
- validatePrimitiveEquality(left, right, location) {
4038
+ inferType(expr, env) {
3370
4039
  if (!this.symbols) {
4040
+ return null;
4041
+ }
4042
+ return inferExprType(expr, env, this.symbols);
4043
+ }
4044
+ requireAssignable(actualType, expectedType, location, message) {
4045
+ if (!this.symbols || !actualType) {
3371
4046
  return;
3372
4047
  }
3373
- const leftClass = classifyComparableExpr(
3374
- left,
3375
- this.ctx.currentActionParamTypes,
3376
- this.symbols
3377
- );
3378
- const rightClass = classifyComparableExpr(
3379
- right,
3380
- this.ctx.currentActionParamTypes,
3381
- this.symbols
3382
- );
3383
- if (leftClass === "nonprimitive" || rightClass === "nonprimitive") {
4048
+ const assignable = isAssignableType(actualType, expectedType, this.symbols);
4049
+ if (assignable === false) {
3384
4050
  this.error(
3385
- "eq/neq operands must be primitive types (null, boolean, number, string)",
4051
+ `${message}, got ${describeTypeExpr(actualType, this.symbols)}`,
3386
4052
  location,
3387
4053
  "E_TYPE_MISMATCH"
3388
4054
  );
3389
4055
  }
3390
4056
  }
4057
+ requireArrayCompatible(actualType, location, fnName) {
4058
+ if (!this.symbols || !actualType) {
4059
+ return;
4060
+ }
4061
+ const outcome = classifyArrayOperand(actualType, this.symbols);
4062
+ if (outcome === false) {
4063
+ this.error(
4064
+ `Function '${fnName}' expects an array first argument, got ${describeTypeExpr(actualType, this.symbols)}`,
4065
+ location,
4066
+ "E_TYPE_MISMATCH"
4067
+ );
4068
+ }
4069
+ }
4070
+ requireLenCompatible(actualType, location) {
4071
+ if (!this.symbols || !actualType) {
4072
+ return;
4073
+ }
4074
+ const outcome = classifyLenOperand(actualType, this.symbols);
4075
+ if (outcome === false) {
4076
+ this.error(
4077
+ `Function 'len' expects a string, array, object, or record argument, got ${describeTypeExpr(actualType, this.symbols)}`,
4078
+ location,
4079
+ "E_TYPE_MISMATCH"
4080
+ );
4081
+ }
4082
+ }
4083
+ validateCoalesceTypes(types, location) {
4084
+ if (!this.symbols) {
4085
+ return;
4086
+ }
4087
+ const concreteTypes = types.map((typeExpr) => stripNullType(typeExpr, this.symbols)).filter((typeExpr) => typeExpr !== null);
4088
+ for (let i = 0; i < concreteTypes.length; i += 1) {
4089
+ for (let j = i + 1; j < concreteTypes.length; j += 1) {
4090
+ const compatible = areTypesCompatible(concreteTypes[i], concreteTypes[j], this.symbols);
4091
+ if (compatible === false) {
4092
+ this.error(
4093
+ `coalesce arguments must have compatible non-null types, got ${describeTypeExpr(concreteTypes[i], this.symbols)} and ${describeTypeExpr(concreteTypes[j], this.symbols)}`,
4094
+ location,
4095
+ "E_TYPE_MISMATCH"
4096
+ );
4097
+ return;
4098
+ }
4099
+ }
4100
+ }
4101
+ }
3391
4102
  error(message, location, code) {
3392
4103
  this.ctx.diagnostics.push({
3393
4104
  severity: "error",
@@ -4635,7 +5346,7 @@ function typeExprToFieldSpec(typeExpr, ctx, seenTypeRefs = []) {
4635
5346
  pushSchemaTypeError(
4636
5347
  ctx,
4637
5348
  "E045",
4638
- `Nullable type '${describeTypeExpr(typeExpr)}' cannot be lowered to FieldSpec`,
5349
+ `Nullable type '${describeTypeExpr2(typeExpr)}' cannot be lowered to FieldSpec`,
4639
5350
  typeExpr.location
4640
5351
  );
4641
5352
  return null;
@@ -4643,7 +5354,7 @@ function typeExprToFieldSpec(typeExpr, ctx, seenTypeRefs = []) {
4643
5354
  pushSchemaTypeError(
4644
5355
  ctx,
4645
5356
  "E043",
4646
- `Union type '${describeTypeExpr(typeExpr)}' cannot be soundly lowered to FieldSpec`,
5357
+ `Union type '${describeTypeExpr2(typeExpr)}' cannot be soundly lowered to FieldSpec`,
4647
5358
  typeExpr.location
4648
5359
  );
4649
5360
  return null;
@@ -4663,7 +5374,7 @@ function typeExprToFieldSpec(typeExpr, ctx, seenTypeRefs = []) {
4663
5374
  pushSchemaTypeError(
4664
5375
  ctx,
4665
5376
  "E046",
4666
- `Record type '${describeTypeExpr(typeExpr)}' cannot be lowered to FieldSpec`,
5377
+ `Record type '${describeTypeExpr2(typeExpr)}' cannot be lowered to FieldSpec`,
4667
5378
  typeExpr.location
4668
5379
  );
4669
5380
  return null;
@@ -4700,20 +5411,20 @@ function pushSchemaTypeError(ctx, code, message, location) {
4700
5411
  location
4701
5412
  });
4702
5413
  }
4703
- function describeTypeExpr(typeExpr) {
5414
+ function describeTypeExpr2(typeExpr) {
4704
5415
  switch (typeExpr.kind) {
4705
5416
  case "simpleType":
4706
5417
  return typeExpr.name;
4707
5418
  case "unionType":
4708
- return typeExpr.types.map((member) => describeTypeExpr(member)).join(" | ");
5419
+ return typeExpr.types.map((member) => describeTypeExpr2(member)).join(" | ");
4709
5420
  case "arrayType":
4710
- return `Array<${describeTypeExpr(typeExpr.elementType)}>`;
5421
+ return `Array<${describeTypeExpr2(typeExpr.elementType)}>`;
4711
5422
  case "recordType":
4712
- return `Record<${describeTypeExpr(typeExpr.keyType)}, ${describeTypeExpr(typeExpr.valueType)}>`;
5423
+ return `Record<${describeTypeExpr2(typeExpr.keyType)}, ${describeTypeExpr2(typeExpr.valueType)}>`;
4713
5424
  case "literalType":
4714
5425
  return JSON.stringify(typeExpr.value);
4715
5426
  case "objectType":
4716
- return `{ ${typeExpr.fields.map((field) => `${field.name}${field.optional ? "?" : ""}: ${describeTypeExpr(field.typeExpr)}`).join("; ")} }`;
5427
+ return `{ ${typeExpr.fields.map((field) => `${field.name}${field.optional ? "?" : ""}: ${describeTypeExpr2(field.typeExpr)}`).join("; ")} }`;
4717
5428
  }
4718
5429
  }
4719
5430
  function validateLiteralAgainstSpec(value, spec, fieldName, location, ctx) {
@@ -6917,4 +7628,4 @@ export {
6917
7628
  compileMelDomain,
6918
7629
  compileMelPatch
6919
7630
  };
6920
- //# sourceMappingURL=chunk-MKLDAZ2Z.js.map
7631
+ //# sourceMappingURL=chunk-4JJQCFJH.js.map