跳到主要内容

语言语义

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") → unknownObject.keys(...) / 形状检查
Symbol.iterator in x→ unknownArray.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.parseJSON.parse('{"port": 3000}') → unknown对象字面量
数值格式化方法(cents / 100).toFixed(2) → unknown(nudo:no-method)Math.round / 算术
String.fromCharCode→ unknown字符串字面量
指数运算符 **→ unknownx * x

Mock 边界(仍建议)​

env 模块与 @types harvester 覆盖了大量常见 Node/Web API,但并不消除对 mock 的需要。下列类别仍建议手写 mock(或保持诚实的 unknown / entry@ 结果)——与 docs/design-limitations.md §八 调用点天花板对齐:

类别为何 mock / 为何 unknown可用办法
Native bindingschild_process.spawn、原生 addon —— env 可有签名,无副作用模拟@nudo:mock,或把返回值当 opaque
动态 require计算模块图无法静态解析@nudo:mock-module / 静态 import
流机器回调Node Transform 内部由运行时驱动,无调用点记录可 harvestmock 流工厂;不要期望内部回调被推断
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逐调用点精度