语言语义
Nudo 通过执行你的代码来推断类型,所以推断质量正好等于求值器 JavaScript 语义的质量。本指南列出求 值器在调用点路径上精确建模的语言行为——下方所有输出块都是对上面代码真实运行 nudo infer 的结果——随后列出仍会退化为 unknown、依赖前需要验证的构造。精确语义也是调用点发现生效的前提:采集到的调用形态只有求值器真的能跟下去才值钱。
已精确建模
字面量上的字符串方法
字面量接收者上的字符串方法在求值期折叠。
function upper() { return "hello".toUpperCase(); }
upper(); // → "HELLO"
function slen() { return "hello".length; }
slen(); // → 5
function sli() { return "hello".slice(1, 3); }
sli(); // → "el"
=== upper ===
call@L2: () => "HELLO"
toUpperCase、toLowerCase、slice、.length 与 split(字面量接收者 + 字面量分隔符)产生精确结果——"a,b,c".split(",") 在调用点路径折叠为 ["a", "b", "c"],无逗号的接收者如 "abc".split("b") 在 @nudo:case 指令路径折叠为 ["a", "c"]。指令路径无法表达含逗号的接收者:指令解析器按逗号拆分用例实参,@nudo:case "split" ("a,b,c") 会变成三个 unknown 形参而非一个字符串。前缀/后缀/包含检查——startsWith、endsWith、includes——对字面量接收者折叠为确定的布尔值。indexOf 只得 number 原语,丢字面量下标。
具体边界的循环
具体边界的 for 循环求值出精确结果。
function sumTo(n) {
let sum = 0;
for (let i = 0; i < n; i++) {
sum = sum + i;
}
return sum;
}
sumTo(5);
=== sumTo ===
call@L8: (5) => 10
具体数组上的 for...of 同样精确:
function sumArr(arr) {
let s = 0;
for (const x of arr) {
s = s + x;
}
return s;
}
sumArr([1, 2, 3]); // → 6
break 保留跳出值
循环跳转是信号:跳出迭代中绑定的值被保留。
function findBig() {
let found;
for (const x of [1, 2, 3, 4]) {
if (x > 2) {
found = x;
break;
}
}
return found;
}
findBig();
=== findBig ===
call@L11: () => 3
结果是字面量 3——循环跳出时绑定的值。
具体形状上的 Object.keys
具体对象上的 Object.keys 返回精确的键元组。
function keysOf() { return Object.keys({ port: 3000, host: "x" }); }
keysOf();
=== keysOf ===
call@L2: () => ["port", "host"]
Math 方法
字面量数值实参上的 Math 方法在求值期折叠——调用点与 @nudo:case 两条路径皆然。
function root(n) { return Math.sqrt(n); }
root(9);
=== root ===
call@L2: (9) => 3
sqrt、pow、abs、floor、ceil、round、sign、min、max 都在字面量实参上折叠为精确数值结果;符号实参拓宽为 number。
原始值转换与解析
全局强制转换构造器与数值解析器在字面量上折叠为精确结果——调用点与 @nudo:case 两条路径皆然:
function strOf(x) { return String(x); }
strOf(5); // → "5"
function boolOf(x) { return Boolean(x); }
boolOf("hi"); // → true
function numOf(x) { return Number(x); }
numOf("42"); // → 42
function intOf(s) { return parseInt(s); }
intOf("42px"); // → 42
function floatOf(s) { return parseFloat(s); }
floatOf("3.14"); // → 3.14
=== strOf ===
call@L2: (5) => "5"
String(x)、Number(x)、Boolean(x) 把 number/string/boolean 字面量折叠为精确强转结果;parseInt(s) / parseFloat(s) 把 string/number 字面量折叠为精确数值前缀/解析结果。符号实参拓宽为目标原语(string / number / boolean)。仓库示例(CI 钉住):docs/examples/algebra/l-primitive-conversion.js。
方法调用与 this
在函数体内进行的方法调用会把 this 绑定到 receiver——调用点与 @nudo:case 两条路径皆然。
class Circle {
constructor(r) { this.radius = r; }
area() { return this.radius * this.radius; }
}
function compute(r) {
const circle = new Circle(r);
return circle.area();
}
compute(5);
=== compute ===
call@L11: (5) => 25
指令路径在实参为字面量时同样精确(对 compute 写 @nudo:case "member" (5) → (5) => 25);空实参表 () 时形参是 unknown,结果退化为 unknown #partial。剩下的缺口在调用点采集而非求值:顶层裸成员调用(circle.area() 作语句)不产生 call@ case——成员被调者不会被采集为调用点。把成员调用包进函数里即可看到。
递归按调用点展开
递归函数按观测到的调用求值:每个顶层调用被完整展开,作为独立的 call@ case 报告精确结果。
function walk(n) {
if (n <= 0) return 0;
return n + walk(n - 1);
}
walk(0);
walk(1);
walk(2);
=== walk ===
call@L6: (0) => 0
call@L7: (1) => 1
call@L8: (2) => 3
Observed: 0 | 1 | 3
超过精确 case 上限的更多调用会聚合为一个实参拓宽的 call@symbolic case。
收窄守卫
=== 比较、typeof、Array.isArray 与 switch 按具体调用点收窄——已验证模式见控制流收窄。
尚未建模
以下构造目前求值为 unknown(通常伴随 nudo:unknown-recv 或 nudo:builtin-unknown 诊断)。请优先使用旁边列出的已建模替代方案。
| 构造 | 当前行为 | 已建模替代 |
|---|---|---|
== / != 字面量折叠 | 1 == "1" → true | 双字面量 Abstract Equality(C2.3) |
| 原始值自动装箱 | "nudo".constructor → unknown | .length、上文的字符串方法 |
Object.prototype 方法 | ({}).hasOwnProperty("key") → unknown | Object.keys(...) / 形状检查 |
Symbol.iterator in x | → unknown | Array.isArray(x) |
Set / Map 上的 for...of | 元素 → unknown | 数组 / .map 回调 |
| Promise 执行器 | new Promise((r) => r("done")) → promise<unknown> | @nudo:mock + async 函数 |
try/catch 形参 | 已建模 — catch (err) 绑定 thrown Abs;new Error("boom") → err.message 为 "boom" | 使用 Error 家族 / 对象 / 字面量 throw |
每迭代 let 闭包 | fns[i]() → unknown | 直接使用迭代结果 |
arguments | → unknown(nudo:builtin-unknown) | 具名参数 |
JSON.parse | JSON.parse('{"port": 3000}') → unknown | 对象字面量 |
| 数值格式化方法 | (cents / 100).toFixed(2) → unknown(nudo:no-method) | Math.round / 算术 |
String.fromCharCode | → unknown | 字符串字面量 |
指数运算符 ** | → unknown | x * x |
Mock 边界(仍建议)
env 模块与 @types harvester 覆盖了大 量常见 Node/Web API,但并不消除对 mock 的需要。下列类别仍建议手写 mock(或保持诚实的 unknown / entry@ 结果)——与 docs/design-limitations.md §八 调用点天花板对齐:
| 类别 | 为何 mock / 为何 unknown | 可用办法 |
|---|---|---|
| Native bindings | child_process.spawn、原生 addon —— env 可有签名,无副作用模拟 | @nudo:mock,或把返回值当 opaque |
动态 require | 计算模块图无法静态解析 | @nudo:mock-module / 静态 import |
| 流机器回调 | Node Transform 内部由运行时驱动,无调用点记录可 harvest | mock 流工厂;不要期望内部回调被推断 |
| browser/node 双入口变体 | 调用点记录不跨文件(归因按文件) | 分析实际发布的入口;另一入口 mock |
| 无调用现场的函数 | 测试未触达的内部 helper → entry@ 兜底 | 补调用现场,或接受 entry@ 为诚实结果 |
| Promise executor 内部 | new Promise((r) => r(...)) → promise<unknown> | @nudo:mock + async 包装 |
覆盖基线(pnpm run coverage:env → docs/reports/env-coverage-baseline.md)报告的是解析率,不是完备性。不要把高解析率当成 soundness 保证——另见 harvester API 中的 mock 边界。
小结
| 能力 | 示例 | 结果 |
|---|---|---|
| 字符串方法 | "hello".toUpperCase() | "HELLO" |
| 具体边界循环 | sumTo(5) | 10 |
break | 循环跳出值 | 3 |
Object.keys | 具体形状 | ["port", "host"] |
| 递归 | walk(2) | 3 |
| 收窄 | typeof / === / Array.isArray / switch | 逐调用点精度 |