跳到主要内容

控制流收窄

当 Nudo 能对某个调用点的具体实参判定条件时,它就会收窄类型。输出中的每一行 call@L… => … 报告一次调用的结果,用该调用的精确实参求值——被收窄消除的分支不会进入该 case 的结果,Observed: 是所有逐调用结果的并集。

收窄在调用点路径(顶层调用、报告为 call@ case)与带具体实参的 @nudo:case 指令上都精确。符号实参(number()、union(...))无法判定条件,其分支会合并而非收窄。下方所有输出块都是对上面代码真实运行 nudo infer 的结果。

比较守卫​

与字面量的比较按调用点收窄实参:每个具体调用只走与实参匹配的分支。

function pickAdult(age) {
if (age >= 18) return age;
return -1;
}
pickAdult(25);
pickAdult(12);
=== pickAdult ===

call@L5: (25) => 25
call@L6: (12) => -1

Observed: 25 | -1

pickAdult(25) 满足 age >= 18,返回 25;pickAdult(12) 落到回退分支返回 -1。合并类型保留两个字面量结果。

判别对象形状​

把属性与字符串字面量比较(shape.kind === "circle")时,匹配调用的分支看到该调用实参的对象形状。

function area(shape) {
if (shape.kind === "circle") {
return shape.radius * 3.14159;
}
return shape.side * shape.side;
}
area({ kind: "circle", radius: 2 });
area({ kind: "square", side: 3 });
=== area ===

call@L7: ({ kind: "circle", radius: 2 }) => 6.28318
call@L8: ({ kind: "square", side: 3 }) => 9

Observed: 6.28318 | 9

circle 调用走 if 分支算出 6.28318;square 调用落到 side * side 得到 9。

typeof 与 Array.isArray() 守卫​

两类守卫都按具体调用分叉,收窄后的值在匹配分支中保持精确行为。

function len(x) {
if (typeof x === "string") return x.length;
if (Array.isArray(x)) return x.length;
return -1;
}
len("abc");
len([1, 2]);
len(5);
=== len ===

call@L7: ("abc") => 3
call@L8: ([1, 2]) => 2
call@L9: (5) => -1

Observed: 3 | 2 | -1

字符串调用在收窄后的字符串上访问 x.length(3),数组调用在收窄后的数组上(2),数字调用穿过两道守卫落到 -1。收窄分支保留的是值本身:对收窄后的数组做索引(x[0])解析为元素类型——字面量数组得到字面量,抽象数组得到元素类型——与 .length 一样。

switch 语句​

对判别字段的 switch 按 case 子句收窄——包括 @nudo:case 指令输入。

/**
* @nudo:case "idle" ({ status: "idle" })
* @nudo:case "loading" ({ status: "loading", requestId: "abc" })
* @nudo:case "success" ({ status: "success", data: { name: "test" } })
* @nudo:case "error" ({ status: "error", message: "fail" })
*/
function handleState(state) {
switch (state.status) {
case "idle": return "Waiting...";
case "loading": return `Loading ${state.requestId}...`;
case "success": return state.data.name;
case "error": return state.message;
}
}
=== handleState ===

debug "idle": ({ status: "idle" }) => "Waiting..."
debug "loading": ({ status: "loading", requestId: "abc" }) => "Loading abc..."
debug "success": ({ status: "success", data: { name: "test" } }) => "test"
debug "error": ({ status: "error", message: "fail" }) => "fail"

Observed: "Waiting..." | "Loading abc..." | "test" | "fail"

每个子句收到匹配的对象形状,因此 state.requestId 与 state.data.name 在各自分支内可以解析。

尚未收窄的模式​

以下模式目前在调用点路径上不会分叉——要么固定落到单个分支,要么退化为 unknown。依赖它们之前请显式加守卫或用 nudo infer 验证:

模式当前行为
条件为 unknown 的三元flag ? "a" : "b" 符号条件不分叉,两支合并(string)。确定条件在两条路径上都精确分叉——pick(true) → "a"、x === 5 ? "five" : "other" 传入 5 → "five"——无需再改用 if 守卫。
符号输入@nudo:case 里的符号实参(number()、union(...))不会分叉条件——分支合并;具体实参在两条路径上都收窄。
in 运算符if ("toJSON" in value) 对对象实参收窄,但方法结果会拓宽(得到 string 而不是闭包的 "serialized");非对象实参还会报告 nudo:no-method。
?. / ??已知属性上的浅层 config.port ?? 3000 得到 number;深层链与短路成员退化为 unknown。

小结​

模式按调用点收窄示例
比较守卫是if (age >= 18) → 25 / -1
判别对象是if (shape.kind === "circle") → 6.28318 / 9
typeof是typeof x === "string" → 3
Array.isArray()是Array.isArray(x) → 2
switch是(含指令输入)逐子句字面量
真值判断是(字面量实参)truthy(42) → "yes"、truthy(0) → "no";undefined/符号实参两支合并
三元是(确定条件)pick(true) → "a";unknown 条件两支合并
in部分分叉,成员结果拓宽
?. / ??部分仅浅层 ??