@nudojs/core 1.11.1 → 1.11.3

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/exec.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as Abs, M as MayThrowEffect, b as Phi } from './run-dg1bW60I.js';
2
- export { W as $assignRecord, X as $call, Y as $callNamed, bI as $nodoPos, Z as $recordBinding, a5 as EvalAbsAssignRecord, a6 as EvalCallRecord, a7 as EvalFallback, g as EvalFallbackStats, a8 as EvalPosRecord, ab as MAX_EVAL_CALL_DEPTH, ac as MAX_EVAL_TOTAL_CALLS, ad as RUNTIME_IMPORT_RE, V as RunTranspiledOptions, af as TranspiledCallResult, at as bindingsOf, aw as callTranspiledExport, ax as callTranspiledExportApply, ay as callTranspiledExportFull, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, aD as evalExprAbs, p as exitEvalCallBudgetSession, v as getEvalCallBudgetState, aG as getEvalCallCollector, w as getEvalFallbackStats, bJ as getPosCollector, aO as isAbsVal, aQ as isCjsMainRun, b5 as noteEvalCallRecord, b6 as noteEvalFallback, bn as resetEvalCallBudget, H as resetEvalFallbackStats, bK as routeApplyThrows, bo as runTranspiled, bp as runTranspiledOptionsMemoKey, bq as setEvalAssignCollector, br as setEvalBindingSink, bs as setEvalCallCollector, bt as setEvalFallbackCollector, bv as setPosCollector, bF as tryRunTranspiled, O as withEvalBudgetOverride } from './run-dg1bW60I.js';
1
+ import { A as Abs, M as MayThrowEffect, b as Phi } from './run-DaqGgSre.js';
2
+ export { W as $assignRecord, X as $call, Y as $callNamed, bI as $nodoPos, Z as $recordBinding, a5 as EvalAbsAssignRecord, a6 as EvalCallRecord, a7 as EvalFallback, g as EvalFallbackStats, a8 as EvalPosRecord, ab as MAX_EVAL_CALL_DEPTH, ac as MAX_EVAL_TOTAL_CALLS, ad as RUNTIME_IMPORT_RE, V as RunTranspiledOptions, af as TranspiledCallResult, at as bindingsOf, aw as callTranspiledExport, ax as callTranspiledExportApply, ay as callTranspiledExportFull, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, aD as evalExprAbs, p as exitEvalCallBudgetSession, v as getEvalCallBudgetState, aG as getEvalCallCollector, w as getEvalFallbackStats, bJ as getPosCollector, aO as isAbsVal, aQ as isCjsMainRun, b5 as noteEvalCallRecord, b6 as noteEvalFallback, bn as resetEvalCallBudget, H as resetEvalFallbackStats, bK as routeApplyThrows, bo as runTranspiled, bp as runTranspiledOptionsMemoKey, bq as setEvalAssignCollector, br as setEvalBindingSink, bs as setEvalCallCollector, bt as setEvalFallbackCollector, bv as setPosCollector, bF as tryRunTranspiled, O as withEvalBudgetOverride } from './run-DaqGgSre.js';
3
3
  import { Node, Expression, File } from '@babel/types';
4
4
 
5
5
  /**
@@ -426,8 +426,12 @@ declare function $get(o: Abs, key: string, opts?: {
426
426
  * 回调返回值丢弃;forEach 表达式值恒 undefined。
427
427
  */
428
428
  declare function $collectionForEach(recv: Abs, cb: unknown): Abs | undefined;
429
- /** 成员写:返回新 obj/brand(不可变更新);frozen/sealed/只读目标按 sloppy 静默失败 */
430
- declare function $set(o: Abs, key: string, value: Abs): Abs;
429
+ /** 成员写:返回新 obj/brand(不可变更新);frozen/sealed/只读目标按 sloppy 静默失败。
430
+ * opts.internal(Bug 84):私有字段落地——槽打 Slot.internal 标记;后续覆写
431
+ * 保留既有标记(this.#x = v 不丢失 internal)。 */
432
+ declare function $set(o: Abs, key: string, value: Abs, opts?: {
433
+ internal?: boolean;
434
+ }): Abs;
431
435
 
432
436
  /**
433
437
  * async / await / generator / ??($nullishTest / $removeNullish)。
@@ -727,6 +731,12 @@ type EvalClassSpec = {
727
731
  name?: string;
728
732
  key?: (thisVal: Abs) => Abs;
729
733
  init: (thisVal: Abs) => Abs;
734
+ /**
735
+ * Bug 84:PrivateName 声明的私有字段(transpile 期标记)——落地槽打
736
+ * Slot.internal,对所有属性 introspection 不可见(内部槽语义);
737
+ * 计算键公共字段(如 ["#x"])不带此标记(同键空间共存)。
738
+ */
739
+ private?: boolean;
730
740
  }>;
731
741
  /**
732
742
  * Bug 60:计算键成员——synth 名 → 键 Abs(定义期求值,求值本身即
package/dist/exec.js CHANGED
@@ -202,7 +202,7 @@ import {
202
202
  withAssumePremises,
203
203
  withEvalBudgetOverride,
204
204
  withExecPhi
205
- } from "./chunk-YRRR6AWI.js";
205
+ } from "./chunk-GL6VOF75.js";
206
206
  export {
207
207
  $absVal,
208
208
  $add,
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { d as AstEnv, A as Abs, b as Phi, T as Term, Q as HofCollectCtx, C as Confidence, S as Shape, a as Pred, R as RelSource, U as HofSite, c as AbsModuleExports, V as RunTranspiledOptions } from './run-dg1bW60I.js';
2
- export { W as $assignRecord, X as $call, Y as $callNamed, Z as $recordBinding, _ as AbsApplyNoThrow, a0 as AbsApplyNoThrowFn, a1 as AbsApplyResult, a2 as AbsApplyReturn, a3 as AbsFnImpl, a4 as AbsSigImpl, a5 as EvalAbsAssignRecord, a6 as EvalCallRecord, a7 as EvalFallback, a8 as EvalPosRecord, a9 as ImplicationOracle, aa as LitValueResult, L as LiteralValue, ab as MAX_EVAL_CALL_DEPTH, ac as MAX_EVAL_TOTAL_CALLS, P as PrimName, ad as RUNTIME_IMPORT_RE, ae as TYPEOF_NAMES, af as TranspiledCallResult, ag as TypeofName, ah as abs, ai as absFunction, aj as absOnly, ak as absToString, al as and, am as anyAbs, an as anyVar, ao as app, ap as assumeFinite, aq as attachFnImpl, ar as bigintLit, as as bindImports, at as bindingsOf, au as bool, av as boolLit, aw as callTranspiledExport, ax as callTranspiledExportApply, ay as callTranspiledExportFull, az as collectAbsExports, aA as confJoin, aB as emptyPhi, aC as eq, aD as evalExprAbs, aE as ge, aF as geNum, aG as getEvalCallCollector, aH as getFnImpl, aI as getImplicationOracle, aJ as gt, aK as gtNum, aL as hostFnCtorFacet, aM as implies, aN as isAbsApplyResult, aO as isAbsVal, aP as isBigPrim, aQ as isCjsMainRun, aR as isExactLit, aS as isNumPrim, aT as isStrPrim, aU as le, aV as leNum, aW as lit, aX as litValue, aY as lt, aZ as ltNum, a_ as makeAbsApplyResult, a$ as markPureFn, b0 as namespaceAbsOf, b1 as ne, b2 as negatePred, b3 as never, b4 as not, b5 as noteEvalCallRecord, b6 as noteEvalFallback, b7 as num, b8 as numLit, b9 as numVar, ba as obj, bb as or, bc as pFalse, bd as pTrue, be as phiAnd, bf as predEquals, bg as predToString, bh as predVars, bi as primToTypeof, bj as ptypeof, bk as pureFnNameOf, bl as relationFingerprint, bm as relationFn, bn as resetEvalCallBudget, bo as runTranspiled, bp as runTranspiledOptionsMemoKey, bq as setEvalAssignCollector, br as setEvalBindingSink, bs as setEvalCallCollector, bt as setEvalFallbackCollector, bu as setImplicationOracle, bv as setPosCollector, bw as shapeOfTerm, bx as shapeOnlyFn, by as shapeToString, bz as simplifyTerm, bA as str, bB as strLit, bC as substPred, bD as termEquals, bE as termToString, bF as tryRunTranspiled, bG as unknown, bH as v } from './run-dg1bW60I.js';
1
+ import { d as AstEnv, A as Abs, b as Phi, T as Term, Q as HofCollectCtx, C as Confidence, S as Shape, a as Pred, R as RelSource, U as HofSite, c as AbsModuleExports, V as RunTranspiledOptions } from './run-DaqGgSre.js';
2
+ export { W as $assignRecord, X as $call, Y as $callNamed, Z as $recordBinding, _ as AbsApplyNoThrow, a0 as AbsApplyNoThrowFn, a1 as AbsApplyResult, a2 as AbsApplyReturn, a3 as AbsFnImpl, a4 as AbsSigImpl, a5 as EvalAbsAssignRecord, a6 as EvalCallRecord, a7 as EvalFallback, a8 as EvalPosRecord, a9 as ImplicationOracle, aa as LitValueResult, L as LiteralValue, ab as MAX_EVAL_CALL_DEPTH, ac as MAX_EVAL_TOTAL_CALLS, P as PrimName, ad as RUNTIME_IMPORT_RE, ae as TYPEOF_NAMES, af as TranspiledCallResult, ag as TypeofName, ah as abs, ai as absFunction, aj as absOnly, ak as absToString, al as and, am as anyAbs, an as anyVar, ao as app, ap as assumeFinite, aq as attachFnImpl, ar as bigintLit, as as bindImports, at as bindingsOf, au as bool, av as boolLit, aw as callTranspiledExport, ax as callTranspiledExportApply, ay as callTranspiledExportFull, az as collectAbsExports, aA as confJoin, aB as emptyPhi, aC as eq, aD as evalExprAbs, aE as ge, aF as geNum, aG as getEvalCallCollector, aH as getFnImpl, aI as getImplicationOracle, aJ as gt, aK as gtNum, aL as hostFnCtorFacet, aM as implies, aN as isAbsApplyResult, aO as isAbsVal, aP as isBigPrim, aQ as isCjsMainRun, aR as isExactLit, aS as isNumPrim, aT as isStrPrim, aU as le, aV as leNum, aW as lit, aX as litValue, aY as lt, aZ as ltNum, a_ as makeAbsApplyResult, a$ as markPureFn, b0 as namespaceAbsOf, b1 as ne, b2 as negatePred, b3 as never, b4 as not, b5 as noteEvalCallRecord, b6 as noteEvalFallback, b7 as num, b8 as numLit, b9 as numVar, ba as obj, bb as or, bc as pFalse, bd as pTrue, be as phiAnd, bf as predEquals, bg as predToString, bh as predVars, bi as primToTypeof, bj as ptypeof, bk as pureFnNameOf, bl as relationFingerprint, bm as relationFn, bn as resetEvalCallBudget, bo as runTranspiled, bp as runTranspiledOptionsMemoKey, bq as setEvalAssignCollector, br as setEvalBindingSink, bs as setEvalCallCollector, bt as setEvalFallbackCollector, bu as setImplicationOracle, bv as setPosCollector, bw as shapeOfTerm, bx as shapeOnlyFn, by as shapeToString, bz as simplifyTerm, bA as str, bB as strLit, bC as substPred, bD as termEquals, bE as termToString, bF as tryRunTranspiled, bG as unknown, bH as v } from './run-DaqGgSre.js';
3
3
  import { Node, File } from '@babel/types';
4
- import { p as parseSource, L as LeakBudget, E as EffectiveInterfaceOpts, b as LoadDepsFingerprint, v as CheckReport, N as NudoConstraint, c as ProjectionBudget } from './projection-budget-B0c9KEhd.js';
5
- export { w as CONSTRAINT_BUILDERS, x as CONSTRAINT_BUILDER_NAMES, y as CONSTRAINT_EXPR_RE, z as CheckAction, C as CheckIssue, A as CheckJson, B as CheckJsonMulti, D as ConstraintBuilder, F as Diagnostic, G as EffectiveInterface, H as GitlabCodeQualityIssue, J as INTERFACE_SOURCE_ABBR, K as InterfaceDiag, I as InterfaceSource, O as InterfaceTierInfo, a as InterfaceTierOpts, Q as NamedImport, R as NudoField, S as NudoFnConstraint, T as NudoSidecarError, U as NudoSig, V as RefineDiag, W as RefineEntry, X as RefineResolveOpts, Y as SELF, Z as actionsForIssue, _ as andC, $ as any, a0 as array, a1 as boolean, a2 as checkArg, a3 as checkCall, a4 as constraintAdmitsNullish, a5 as constraintToEntryAbs, a6 as derefConstraint, a7 as drainPromiseMicros, a8 as effectiveInterface, a9 as enterPromiseExecutorScope, aa as evalPromiseCtor, ab as evalPromiseMethod, ac as evalPromiseStatic, ad as execNudoModule, ae as extractDeclaredThrows, af as extractFnBudget, ag as extractNudoImports, ah as extractRefineReturnFromSource, ai as extractRefinesFromSource, aj as fn, ak as fnConstraintToEntryReqs, al as formatCheckReport, am as formatConstraint, an as formatDiagnostics, ao as formatEffectiveInterfaceDisplay, ap as formatGithubAnnotations, aq as formatGitlabCodeQuality, ar as formatInterfaceTierLine, as as generatedExportNames, at as getParseSourceCacheSize, au as getTerm, av as instantiateConstraint, aw as interfaceDiagCount, ax as interfaceSourceOf, ay as interfaceTierOf, az as isIntFlag, aA as isNodeModulesPath, aB as isNudoConstraint, aC as lazy, aD as leavePromiseExecutorScope, aE as lenTerm, aF as litC, aG as localNamedExports, aH as notePromiseExecutorFork, aI as nullable, aJ as number, aK as omit, aL as partial, aM as pick, aN as queuePromiseMicro, aO as refineDiagCount, aP as refineToIndexedFull, aQ as resetNudoModuleExecCache, aR as resetParseSourceCache, aS as resetSidecarLoadFailureCache, aT as serializeCheckJson, aU as serializeCheckJsonMulti, aV as setInterfaceDiagCollector, aW as setRefineDiagCollector, aX as shape, aY as sidecarClosureFingerprint, aZ as sidecarPathOf, a_ as string, a$ as takeInterfaceDiags, b0 as takeInterfaceDiagsSince, b1 as takeRefineDiags, b2 as takeRefineDiagsSince, b3 as throwConstraintToKinds, b4 as union } from './projection-budget-B0c9KEhd.js';
4
+ import { p as parseSource, L as LeakBudget, E as EffectiveInterfaceOpts, b as LoadDepsFingerprint, v as CheckReport, N as NudoConstraint, c as ProjectionBudget } from './projection-budget-CeoxA06q.js';
5
+ export { w as CONSTRAINT_BUILDERS, x as CONSTRAINT_BUILDER_NAMES, y as CONSTRAINT_EXPR_RE, z as CheckAction, C as CheckIssue, A as CheckJson, B as CheckJsonMulti, D as ConstraintBuilder, F as Diagnostic, G as EffectiveInterface, H as GitlabCodeQualityIssue, J as INTERFACE_SOURCE_ABBR, K as InterfaceDiag, I as InterfaceSource, O as InterfaceTierInfo, a as InterfaceTierOpts, Q as NamedImport, R as NudoField, S as NudoFnConstraint, T as NudoSidecarError, U as NudoSig, V as RefineDiag, W as RefineEntry, X as RefineResolveOpts, Y as SELF, Z as actionsForIssue, _ as andC, $ as any, a0 as array, a1 as boolean, a2 as checkArg, a3 as checkCall, a4 as constraintAdmitsNullish, a5 as constraintToEntryAbs, a6 as derefConstraint, a7 as drainPromiseMicros, a8 as effectiveInterface, a9 as enterPromiseExecutorScope, aa as evalPromiseCtor, ab as evalPromiseMethod, ac as evalPromiseStatic, ad as execNudoModule, ae as extractDeclaredThrows, af as extractFnBudget, ag as extractNudoImports, ah as extractRefineReturnFromSource, ai as extractRefinesFromSource, aj as fn, ak as fnConstraintToEntryReqs, al as formatCheckReport, am as formatConstraint, an as formatDiagnostics, ao as formatEffectiveInterfaceDisplay, ap as formatGithubAnnotations, aq as formatGitlabCodeQuality, ar as formatInterfaceTierLine, as as generatedExportNames, at as getParseSourceCacheSize, au as getTerm, av as instantiateConstraint, aw as interfaceDiagCount, ax as interfaceSourceOf, ay as interfaceTierOf, az as isIntFlag, aA as isNodeModulesPath, aB as isNudoConstraint, aC as lazy, aD as leavePromiseExecutorScope, aE as lenTerm, aF as litC, aG as localNamedExports, aH as notePromiseExecutorFork, aI as nullable, aJ as number, aK as omit, aL as partial, aM as pick, aN as queuePromiseMicro, aO as refineDiagCount, aP as refineToIndexedFull, aQ as resetNudoModuleExecCache, aR as resetParseSourceCache, aS as resetSidecarLoadFailureCache, aT as serializeCheckJson, aU as serializeCheckJsonMulti, aV as setInterfaceDiagCollector, aW as setRefineDiagCollector, aX as shape, aY as sidecarClosureFingerprint, aZ as sidecarPathOf, a_ as string, a$ as takeInterfaceDiags, b0 as takeInterfaceDiagsSince, b1 as takeRefineDiags, b2 as takeRefineDiagsSince, b3 as throwConstraintToKinds, b4 as union } from './projection-budget-CeoxA06q.js';
6
6
  export { $add, $arguments, $arr, $arrMutContainer, $arrRest, $arrWithHoles, $async, $asyncReturn, $await, $bitand, $bitnot, $bitor, $bitxor, $catchVal, $class, $classExpr, $collectionForEach, $concat, $copy, $del, $delRes, $div, $dynamicImport, $elems, $eq, $eqLoose, $fnVal, $for, $forInKeys, $forIter, $forOf, $fork, $ge, $gen, $get, $gt, $idx, $idxSet, $importMeta, $in, $instanceof, $instanceofNonIdent, $invoke, $invokeSuper, $isBreakTo, $isForkExit, $join, $le, $len, $lit, $loopBreak, $loopContinue, $loopReturn, $lt, $mod, $mul, $ne, $neLoose, $neg, $new, $not, $nullishTest, $obj, $objAccessor, $objRest, $optionalGet, $optionalInvoke, $orDefault, $pow, $pushLoopExit, $rawThis, $reStateCall, $regex, $rethrowIfNudoReturn, $set, $setKey, $shl, $shr, $spread, $staticInvoke, $sub, $super, $switch, $thisGet, $thisSet, $throw, $toNumber, $toNumeric, $tryCurrentMark, $tryDetachSoftCatch, $tryDigestSoftCatch, $tryDiscardSoft, $tryMark, $tryOrphanSoft, $tryPopMark, $tryReleaseSoftCatch, $tryReleaseSoftOut, $tryTakeSince, $typeof, $unknown, $updateAdd, $updateSub, $ushr, $while, $whileSeq, $yield, DEFAULT_MAX_LOOP_ITERS, EvalClassSpec, NudoLoopSignal, NudoReturn, NudoThrow, NudoUnsupportedError, TranspileOptions, asAbsVal, callAtFunctionBoundary, clearBClasses, clearStaleTermPred, currentExecPhi, fillTuple, foldRequireSpecArg, foldStaticStringExpr, getEvalClass, isArrMutator, isDefinitelyFalse, isDefinitelyTrue, isNudoBreak, isNudoContinue, isNudoReturn, isNudoThrow, litTruth, lookupObjAccessor, namespaceNameOf, pushLoopExit, pushThrowExit, registerEvalClass, runWithLoopExits, runtimeImportOf, takeLoopExits, takeThrowExits, transpile, transpileBodyNode, transpileExpression, transpileFile, transpileSource, withExecPhi } from './exec.js';
7
7
 
8
8
  /**
@@ -567,10 +567,18 @@ declare function getAbsProperty(recv: Abs, name: string): Abs | undefined;
567
567
  * 纪律:积之和默认;optional 塌缩是显式损失;函数并保输入→输出相关性。
568
568
  */
569
569
 
570
+ /**
571
+ * Bug 84:internal 标记私有槽(私有字段 "#x" 混淆键落实例内层 obj)。
572
+ * 原生私有字段是内部槽,对所有属性 introspection(for-in / hasOwnProperty
573
+ * / keys / gOPN / spread / assign / stringify)不可见;$get/$set 直连键
574
+ * 访问与 $in 槽存在性查询不受影响(Bug 78 语义)。必须用槽标志而非
575
+ * 名字前缀过滤——合法公共字符串键 {"#x":1} 与私有槽同键空间共存。
576
+ */
570
577
  type Slot = {
571
578
  value: Abs;
572
579
  optional?: boolean;
573
580
  readonly?: boolean;
581
+ internal?: boolean;
574
582
  };
575
583
  /** ES 规范数组下标(无前导零、< 2^32-1);非规范键返回 undefined */
576
584
  declare function canonicalArrayIndex(v: unknown): number | undefined;
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ import {
65
65
  takeInterfaceDiagsSince,
66
66
  takeRefineDiags,
67
67
  takeRefineDiagsSince
68
- } from "./chunk-5VGO4YJT.js";
68
+ } from "./chunk-2PFT7GU2.js";
69
69
  import {
70
70
  $add,
71
71
  $arguments,
@@ -183,6 +183,7 @@ import {
183
183
  DEFAULT_MAX_LOOP_ITERS,
184
184
  FORK_TRUNCATION_LABEL,
185
185
  HOST_EFFECT_LABEL_PREFIX,
186
+ LOOP_TRUNCATION_LABEL,
186
187
  MATH_FOLD_ERROR_LABEL,
187
188
  MAX_EVAL_CALL_DEPTH,
188
189
  MAX_EVAL_TOTAL_CALLS,
@@ -524,7 +525,7 @@ import {
524
525
  withAssumePremises,
525
526
  withExecPhi,
526
527
  withVar
527
- } from "./chunk-YRRR6AWI.js";
528
+ } from "./chunk-GL6VOF75.js";
528
529
 
529
530
  // src/environment.ts
530
531
  function createEnvironment(parent, bindings = /* @__PURE__ */ new Map()) {
@@ -2210,9 +2211,13 @@ function checkSourceInScope(filePath, source, phi, opts) {
2210
2211
  const file = parseSource(source);
2211
2212
  const names = listTopFunctions(source, file);
2212
2213
  const memoPaths = sidecarFp !== void 0 && callOpts.fromFile ? [.../* @__PURE__ */ new Set([...depsFp.paths, normPath(sidecarPathOf(callOpts.fromFile))])] : depsFp.paths;
2213
- const truncated = /* @__PURE__ */ new Set();
2214
+ const truncated = /* @__PURE__ */ new Map();
2214
2215
  resetAbsCallBudget();
2215
- const prevTrunc = setAbsTruncationCollector((label) => truncated.add(label));
2216
+ const prevTrunc = setAbsTruncationCollector((label, reason) => {
2217
+ const reasons = truncated.get(label) ?? /* @__PURE__ */ new Set();
2218
+ if (reason) reasons.add(reason);
2219
+ truncated.set(label, reasons);
2220
+ });
2216
2221
  try {
2217
2222
  const prevFaceResolver = setContractFaceResolver(
2218
2223
  makeSameFileFaceResolver(callOpts, filePath, source, file)
@@ -2691,7 +2696,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2691
2696
  });
2692
2697
  }
2693
2698
  }
2694
- for (const label of truncated) {
2699
+ for (const [label, reasons] of truncated) {
2695
2700
  if (label === FORK_TRUNCATION_LABEL) {
2696
2701
  issues.push({
2697
2702
  severity: "info",
@@ -2739,11 +2744,24 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2739
2744
  });
2740
2745
  continue;
2741
2746
  }
2747
+ if (label === LOOP_TRUNCATION_LABEL) {
2748
+ issues.push({
2749
+ severity: "info",
2750
+ code: "nudo:loop-truncated",
2751
+ message: `A loop did not terminate within its iteration budget (abstract unroll cap reached and the exit condition stayed concrete-true); affected loop state was widened (bounded, not exact)`,
2752
+ suggestion: "optional: restructure so the exit condition becomes abstract (data-dependent) within a few iterations, or pin the result with @nudo:case / @nudo:contract \u2014 non-blocking"
2753
+ });
2754
+ continue;
2755
+ }
2756
+ const allCycle = reasons.size > 0 && [...reasons].every((r) => r === "cycle");
2757
+ const depth = reasons.has("depth");
2758
+ const calls = reasons.has("calls");
2742
2759
  issues.push({
2743
2760
  severity: "info",
2744
2761
  code: "nudo:recursion-truncated",
2745
- message: `Recursive evaluation of '${label}' was truncated (depth/size budget); result widened to unknown#opaque (budget \u2014 not inference debt)`,
2746
- suggestion: "raise the function budget with @nudo:budget calls=\u2026 depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)",
2762
+ message: allCycle ? `Recursive evaluation of '${label}' hit a call cycle (same abstract arguments already on the stack); result widened to unknown#opaque (cycle \u2014 budget cannot lift this)` : `Recursive evaluation of '${label}' was truncated (${depth && calls ? "depth/size budget" : depth ? "call-depth budget" : calls ? "total-calls budget" : "depth/size budget"}); result widened to unknown#opaque (budget \u2014 not inference debt)`,
2763
+ suggestion: allCycle ? "a call cycle is not a budget: make the recursion arguments vary per level, narrow the base case, or declare @nudo:contract \u2014 raising @nudo:budget will not change this" : depth && !calls ? "raise the function budget with @nudo:budget depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)" : calls && !depth ? "raise the function budget with @nudo:budget calls=\u2026 (or narrow the recursion base case / declare @nudo:contract return)" : "raise the function budget with @nudo:budget calls=\u2026 depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)",
2764
+ reason: allCycle ? "cycle" : depth && calls ? "budget" : depth ? "depth" : calls ? "calls" : "budget",
2747
2765
  fn: label
2748
2766
  });
2749
2767
  }
@@ -2853,15 +2871,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2853
2871
  ok: errors === 0,
2854
2872
  signatures,
2855
2873
  summary: { errors, warnings, infos, functions: signatures.length },
2856
- budget: {
2857
- truncated: budget.truncated,
2858
- callTruncated: budget.callTruncated,
2859
- forkTruncated: budget.forkTruncated,
2860
- calls: budget.calls,
2861
- maxCalls: budget.maxCalls,
2862
- forks: budget.forks,
2863
- maxForks: budget.maxForks
2864
- }
2874
+ budget: { ...budget }
2865
2875
  };
2866
2876
  }
2867
2877
  function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay) {
@@ -2990,7 +3000,6 @@ function actionsForIssue(i) {
2990
3000
  { kind: "info", label: "add a call site or @nudo:case" }
2991
3001
  ];
2992
3002
  case "nudo:fork-truncated":
2993
- case "nudo:recursion-truncated":
2994
3003
  case "nudo:promise-micro-truncated":
2995
3004
  case "nudo:promise-micro-error":
2996
3005
  return [
@@ -2999,6 +3008,20 @@ function actionsForIssue(i) {
2999
3008
  label: "results widened to unknown \u2014 raise budget (@nudo:budget forks/calls/depth on the function) or narrow control flow"
3000
3009
  }
3001
3010
  ];
3011
+ case "nudo:loop-truncated":
3012
+ return [
3013
+ {
3014
+ kind: "info",
3015
+ label: "loop state widened \u2014 the exit condition stayed concrete-true past the iteration cap; make the bound data-dependent (abstract) or pin the result with @nudo:case / @nudo:contract"
3016
+ }
3017
+ ];
3018
+ case "nudo:recursion-truncated":
3019
+ return [
3020
+ {
3021
+ kind: "info",
3022
+ label: i.reason === "cycle" ? "call cycle: same abstract arguments already on the stack \u2014 raising @nudo:budget cannot lift it; make the arguments vary per recursion level or declare @nudo:contract" : i.reason === "depth" || i.reason === "calls" || i.reason === "budget" ? `results widened to unknown \u2014 raise budget (@nudo:budget ${i.reason === "depth" ? "depth" : i.reason === "calls" ? "calls" : "calls/depth"} on the function) or narrow the recursion` : "results widened to unknown \u2014 raise budget (@nudo:budget calls/depth on the function) or narrow control flow"
3023
+ }
3024
+ ];
3002
3025
  case "nudo:host-effect-blocked":
3003
3026
  return [
3004
3027
  { kind: "mock", label: "pin the host API with @nudo:mock / @nudo:env" },
@@ -3052,6 +3075,7 @@ function serializeCheckJson(r) {
3052
3075
  ...i.actual !== void 0 ? { actual: i.actual } : {},
3053
3076
  ...i.expected !== void 0 ? { expected: i.expected } : {},
3054
3077
  ...i.suggestion !== void 0 ? { suggestion: i.suggestion } : {},
3078
+ ...i.reason !== void 0 ? { reason: i.reason } : {},
3055
3079
  ...acts.length > 0 ? { actions: acts } : {}
3056
3080
  };
3057
3081
  }),
@@ -3072,6 +3096,12 @@ function serializeCheckJsonMulti(reports) {
3072
3096
  let maxForks = 0;
3073
3097
  let callTruncated = false;
3074
3098
  let forkTruncated = false;
3099
+ let evalCalls = 0;
3100
+ let maxEvalCalls = 0;
3101
+ let evalCallTruncated = false;
3102
+ let cycleTruncated = false;
3103
+ let loopTruncated = false;
3104
+ const reasons = /* @__PURE__ */ new Set();
3075
3105
  for (const r of reports) {
3076
3106
  summary.errors += r.summary.errors;
3077
3107
  summary.warnings += r.summary.warnings;
@@ -3084,9 +3114,15 @@ function serializeCheckJsonMulti(reports) {
3084
3114
  maxForks = Math.max(maxForks, r.budget.maxForks);
3085
3115
  callTruncated = callTruncated || r.budget.callTruncated;
3086
3116
  forkTruncated = forkTruncated || r.budget.forkTruncated;
3117
+ evalCalls += r.budget.evalCalls ?? 0;
3118
+ maxEvalCalls = Math.max(maxEvalCalls, r.budget.maxEvalCalls ?? 0);
3119
+ evalCallTruncated = evalCallTruncated || (r.budget.evalCallTruncated ?? false);
3120
+ cycleTruncated = cycleTruncated || (r.budget.cycleTruncated ?? false);
3121
+ loopTruncated = loopTruncated || (r.budget.loopTruncated ?? false);
3122
+ for (const x of r.budget.reasons ?? []) reasons.add(x);
3087
3123
  }
3088
3124
  }
3089
- const budgetTruncated = callTruncated || forkTruncated;
3125
+ const budgetTruncated = callTruncated || evalCallTruncated || forkTruncated || loopTruncated;
3090
3126
  return {
3091
3127
  version: 1,
3092
3128
  kind: "multi",
@@ -3103,7 +3139,13 @@ function serializeCheckJsonMulti(reports) {
3103
3139
  calls,
3104
3140
  maxCalls,
3105
3141
  forks,
3106
- maxForks
3142
+ maxForks,
3143
+ evalCalls,
3144
+ maxEvalCalls,
3145
+ evalCallTruncated,
3146
+ cycleTruncated,
3147
+ loopTruncated,
3148
+ reasons: [...reasons].sort()
3107
3149
  }
3108
3150
  } : {},
3109
3151
  reports
@@ -3125,13 +3167,14 @@ function formatCheckReport(r, opts = {}) {
3125
3167
  ` ${r.summary.errors} error \xB7 ${r.summary.warnings} warning \xB7 ${r.summary.infos} info \xB7 ${r.summary.functions} fn`
3126
3168
  );
3127
3169
  if (r.budget?.truncated) {
3170
+ const onlyLoop = r.budget.loopTruncated && !r.budget.callTruncated && !r.budget.evalCallTruncated && !r.budget.forkTruncated;
3128
3171
  lines.push("");
3129
3172
  lines.push("budget");
3130
3173
  lines.push(
3131
- ` truncated calls ${r.budget.calls}/${r.budget.maxCalls} \xB7 forks ${r.budget.forks}/${r.budget.maxForks}${r.budget.callTruncated ? " (calls)" : ""}${r.budget.forkTruncated ? " (forks)" : ""}`
3174
+ ` truncated calls ${r.budget.calls}/${r.budget.maxCalls} \xB7 eval calls ${r.budget.evalCalls}/${r.budget.maxEvalCalls} \xB7 forks ${r.budget.forks}/${r.budget.maxForks}${r.budget.callTruncated ? " (calls)" : ""}${r.budget.evalCallTruncated ? " (eval calls)" : ""}${r.budget.forkTruncated ? " (forks)" : ""}${r.budget.loopTruncated ? " (loop iters)" : ""}`
3132
3175
  );
3133
3176
  lines.push(
3134
- ` \u2192 some results widened to unknown (not a contract failure) \u2014 raise nudo.analysis.maxForks / simplify recursion`
3177
+ r.budget.cycleTruncated ? ` \u2192 some results widened to unknown (not a contract failure) \u2014 includes call cycles (same abstract args; a budget cannot lift them)` : onlyLoop ? ` \u2192 loop state widened (iteration budget, not a contract failure) \u2014 @nudo:budget does not apply; make the bound data-dependent or pin with @nudo:case / @nudo:contract` : ` \u2192 some results widened to unknown (not a contract failure) \u2014 raise @nudo:budget / nudo.analysis.maxForks or simplify recursion`
3135
3178
  );
3136
3179
  }
3137
3180
  if (r.signatures.length > 0) {
@@ -1,7 +1,7 @@
1
- import { I as InterfaceSource, a as InterfaceTierOpts, p as parseSource, C as CheckIssue } from './projection-budget-B0c9KEhd.js';
2
- export { L as LeakBudget, b as LoadDepsFingerprint, M as MAX_PROMISE_MICROS, P as PROJECTION_MAX_DEPTH, c as ProjectionBudget, d as ProjectionStopReason, e as defaultLeakBudget, f as exceedsBudget, g as exportedNameOfLocal, h as extractAllLoadSpecs, i as getPromiseMicrosLength, l as leakIfNeeded, j as loadModuleDepsFingerprint, m as maybeLeak, n as normPath, r as resetLeakCounter, k as resolveDepPath, s as sidecarSpecsOf, o as stablePathKey, q as stablePathKeyGraph, t as termDepth, u as termNodes } from './projection-budget-B0c9KEhd.js';
3
- import { A as Abs, T as Term, c as AbsModuleExports, d as AstEnv } from './run-dg1bW60I.js';
4
- export { $ as $tryDigestSoft, e as $tryMarkSoft, f as $tryReleaseSoft, E as ERROR_FAMILY, g as EvalFallbackStats, M as MayThrowEffect, h as absLit, i as absNumLit, j as absStrLit, k as allAbsStr, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, o as errorTypeAbs, p as exitEvalCallBudgetSession, q as filterDeclaredThrows, r as filterGateThrows, s as filterIgnoredThrows, t as flushMayThrowEffects, u as formatThrowsAbs, v as getEvalCallBudgetState, w as getEvalFallbackStats, x as getMayThrowCollector, y as isThrowsIgnored, z as mayThrowEffectsToAbs, B as orphanMayThrowEffects, D as popMayThrowFrame, F as pushMayThrowFrame, G as recordMayThrow, H as resetEvalFallbackStats, I as runWithMayThrowSession, J as setMayThrowCollector, K as throwAbsToKinds, N as throwsKindCovered, O as withEvalBudgetOverride } from './run-dg1bW60I.js';
1
+ import { I as InterfaceSource, a as InterfaceTierOpts, p as parseSource, C as CheckIssue } from './projection-budget-CeoxA06q.js';
2
+ export { L as LeakBudget, b as LoadDepsFingerprint, M as MAX_PROMISE_MICROS, P as PROJECTION_MAX_DEPTH, c as ProjectionBudget, d as ProjectionStopReason, e as defaultLeakBudget, f as exceedsBudget, g as exportedNameOfLocal, h as extractAllLoadSpecs, i as getPromiseMicrosLength, l as leakIfNeeded, j as loadModuleDepsFingerprint, m as maybeLeak, n as normPath, r as resetLeakCounter, k as resolveDepPath, s as sidecarSpecsOf, o as stablePathKey, q as stablePathKeyGraph, t as termDepth, u as termNodes } from './projection-budget-CeoxA06q.js';
3
+ import { A as Abs, T as Term, c as AbsModuleExports, d as AstEnv } from './run-DaqGgSre.js';
4
+ export { $ as $tryDigestSoft, e as $tryMarkSoft, f as $tryReleaseSoft, E as ERROR_FAMILY, g as EvalFallbackStats, M as MayThrowEffect, h as absLit, i as absNumLit, j as absStrLit, k as allAbsStr, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, o as errorTypeAbs, p as exitEvalCallBudgetSession, q as filterDeclaredThrows, r as filterGateThrows, s as filterIgnoredThrows, t as flushMayThrowEffects, u as formatThrowsAbs, v as getEvalCallBudgetState, w as getEvalFallbackStats, x as getMayThrowCollector, y as isThrowsIgnored, z as mayThrowEffectsToAbs, B as orphanMayThrowEffects, D as popMayThrowFrame, F as pushMayThrowFrame, G as recordMayThrow, H as resetEvalFallbackStats, I as runWithMayThrowSession, J as setMayThrowCollector, K as throwAbsToKinds, N as throwsKindCovered, O as withEvalBudgetOverride } from './run-DaqGgSre.js';
5
5
  import { File, Node } from '@babel/types';
6
6
 
7
7
  /**
@@ -60,6 +60,13 @@ declare function sourceHasCjsExports(source: string): boolean;
60
60
  declare const MAX_CALL_DEPTH = 64;
61
61
  /** 与 B 命名调用(calls.ts MAX_EVAL_TOTAL_CALLS)同阀:病态展开下 200k 级不可接受 */
62
62
  declare const MAX_TOTAL_CALLS = 20000;
63
+ /**
64
+ * 调用截断归因(issue #158):
65
+ * - `cycle`:同 name+arg 指纹的调用已在栈上——**不是预算**,抬高
66
+ * `@nudo:budget` 永远解不开(数据依赖自递归在抽象层就是不动点);
67
+ * - `depth` / `calls`:真预算超限,`@nudo:budget` 可抬。
68
+ */
69
+ type AbsTruncationReason = "cycle" | "depth" | "calls";
63
70
  /** A2:预算/截断快照(check --json / health 上屏) */
64
71
  type AbsBudgetStats = {
65
72
  calls: number;
@@ -70,6 +77,18 @@ type AbsBudgetStats = {
70
77
  forkTruncated: boolean;
71
78
  /** 任一预算截断 → true(结果可能已 widen 成 unknown) */
72
79
  truncated: boolean;
80
+ /** B 命名调用(exec $callNamed)累计进入次数——宿主入口内跨执行会话累计 */
81
+ evalCalls: number;
82
+ /** B 命名调用当前生效上限 */
83
+ maxEvalCalls: number;
84
+ /** B 命名调用发生截断(cycle/depth/calls 任一) */
85
+ evalCallTruncated: boolean;
86
+ /** 任一路径的 cycle 截断(预算抬不动的那类;issue #158) */
87
+ cycleTruncated: boolean;
88
+ /** 循环迭代预算截断(#loop-iterations;maxIters/硬上限,非调用预算) */
89
+ loopTruncated: boolean;
90
+ /** 调用截断原因集合(A+B;稳定排序,进 JSON) */
91
+ reasons: AbsTruncationReason[];
73
92
  };
74
93
  declare function getAbsCallBudgetStats(): AbsBudgetStats;
75
94
  /** 宿主入口(runTranspiled / callTranspiledExportFull / check)前重置 */
@@ -95,9 +114,11 @@ declare function truncatedAbs(): Abs;
95
114
  declare function callBudgetKey(kind: string, id: string, args: unknown[]): string;
96
115
  /** 记录被截断的递归(service 可映射为 nudo:recursion-truncated)。
97
116
  * 返回先前 collector,便于嵌套 save/restore。 */
98
- declare function setAbsTruncationCollector(collector: ((fnLabel: string) => void) | null): ((fnLabel: string) => void) | null;
99
- declare function noteAbsTruncation(label: string): void;
100
- /** 进入调用:超限/cycle 则不执行 body,返回 opaque */
117
+ declare function setAbsTruncationCollector(collector: ((fnLabel: string, reason?: AbsTruncationReason) => void) | null): ((fnLabel: string, reason?: AbsTruncationReason) => void) | null;
118
+ declare function noteAbsTruncation(label: string, reason?: AbsTruncationReason): void;
119
+ /** 进入调用:cycle/超限则不执行 body,返回 opaque。
120
+ * 归因顺序 = 守卫顺序(cycle 先于 depth 先于 calls):同指纹调用已在栈上
121
+ * 时无论预算多深都会立即截断——报 cycle 而不是预算(#158)。 */
101
122
  declare function enterCall(key: string, label: string): boolean;
102
123
  declare function exitCall(): void;
103
124
  /**
package/dist/internal.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  stableAnalyzeKeySource,
22
22
  stablePathKey,
23
23
  stablePathKeyGraph
24
- } from "./chunk-5VGO4YJT.js";
24
+ } from "./chunk-2PFT7GU2.js";
25
25
  import {
26
26
  $tryDigestSoft,
27
27
  $tryMarkSoft,
@@ -169,7 +169,7 @@ import {
169
169
  withEvalBudgetOverride,
170
170
  withFnBudgetOverride,
171
171
  wrapPromise
172
- } from "./chunk-YRRR6AWI.js";
172
+ } from "./chunk-GL6VOF75.js";
173
173
 
174
174
  // src/algebra/inlay.ts
175
175
  function listFunctions(source) {
@@ -1,5 +1,5 @@
1
1
  import { File } from '@babel/types';
2
- import { A as Abs, T as Term, P as PrimName, a as Pred, L as LiteralValue, b as Phi, C as Confidence } from './run-dg1bW60I.js';
2
+ import { A as Abs, T as Term, P as PrimName, a as Pred, L as LiteralValue, b as Phi, C as Confidence } from './run-DaqGgSre.js';
3
3
 
4
4
  declare function resetParseSourceCache(): void;
5
5
  declare function getParseSourceCacheSize(): number;
@@ -670,6 +670,12 @@ type CheckIssue = Diagnostic & {
670
670
  actual?: string;
671
671
  /** 期望约束(Pred 或 Abs 展示) */
672
672
  expected?: string;
673
+ /**
674
+ * 截断归因(`nudo:recursion-truncated`,issue #158):
675
+ * `cycle`=同抽象实参调用环(抬预算无效);`depth`/`calls`=真预算超限;
676
+ * `budget`=混合(cycle+预算或 depth+calls 同报)。
677
+ */
678
+ reason?: "cycle" | "depth" | "calls" | "budget";
673
679
  /**
674
680
  * AI1:结构化下一步(additive)。agent 优先消费 `actions`,
675
681
  * 不必解析 `suggestion` 散文;`command` 可直接执行。
@@ -703,6 +709,8 @@ type CheckReport = {
703
709
  /**
704
710
  * A2 预算/截断可观测(additive)。`truncated=true` 时部分结果可能已
705
711
  * widen 成 unknown——不是契约失败,是引擎预算。
712
+ * evalCalls/evalCallTruncated = B 命名调用($callNamed)路径;
713
+ * cycleTruncated/reasons = 归因(#158):cycle 抬 @nudo:budget 无效。
706
714
  */
707
715
  budget?: {
708
716
  truncated: boolean;
@@ -712,6 +720,13 @@ type CheckReport = {
712
720
  maxCalls: number;
713
721
  forks: number;
714
722
  maxForks: number;
723
+ evalCalls: number;
724
+ maxEvalCalls: number;
725
+ evalCallTruncated: boolean;
726
+ cycleTruncated: boolean;
727
+ /** 循环迭代预算截断(#loop-iterations;maxIters/硬上限,非调用预算) */
728
+ loopTruncated: boolean;
729
+ reasons: string[];
715
730
  };
716
731
  };
717
732
  /**
@@ -744,6 +759,8 @@ type CheckJson = {
744
759
  actual?: string;
745
760
  expected?: string;
746
761
  suggestion?: string;
762
+ /** 截断归因(nudo:recursion-truncated;issue #158,additive) */
763
+ reason?: CheckIssue["reason"];
747
764
  /** AI1:结构化 next-action(只增字段) */
748
765
  actions?: CheckAction[];
749
766
  }>;
@@ -756,6 +773,7 @@ declare function actionsForIssue(i: {
756
773
  expected?: string;
757
774
  suggestion?: string;
758
775
  fn?: string;
776
+ reason?: string;
759
777
  }): CheckAction[];
760
778
  /** 多文件 `check --json` 信封(CI / monorepo)。单文件仍输出裸 CheckJson。 */
761
779
  type CheckJsonMulti = {
@@ -155,10 +155,12 @@ type Shape = {
155
155
  type: PrimName;
156
156
  } | {
157
157
  k: "obj";
158
+ /** 槽形与 objects.ts Slot 同构(含 Bug 84 internal 私有槽标记) */
158
159
  slots: Record<string, {
159
160
  value: Abs;
160
161
  optional?: boolean;
161
162
  readonly?: boolean;
163
+ internal?: boolean;
162
164
  }>;
163
165
  index?: {
164
166
  key: Abs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nudojs/core",
3
- "version": "1.11.1",
3
+ "version": "1.11.3",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },