feat(types): 宽度事实表共享给编译器与 LSP
新增 lib/types/IntFacts.vp:显式宽度范围检查、最小可存宽度、最小二幂宽度统一到一张表。ExprGenConst 与 define_min_width 改用它;LSP hover 的位宽推荐/后缀适配判定也改用它,任意 _iN/_uN 后缀可显示,但推荐宽度仍只给二幂值。gate10 增加 128_u8、1_u100、128_u7 与数字分隔符 hover 断言;计划文档记录 Pylance 式诊断等级等决策。
This commit is contained in:
1 parent
d1f84371f8
commit
9d298b5ae9
7 files changed
+358
-45
No files matched your search
@@ -15,6 +15,7 @@ import lib.core.Gen.Generator as Generator
|
||||
import lib.core.CompilerFactory as CompilerFactory
|
||||
import lib.Stub.StubGen.VpiReader as VpiReader
|
||||
import lib.core.Gen.GenApi as GenApi
|
||||
import lib.types.IntFacts as IntFacts
|
||||
|
||||
# AssignGenMixin - assignment translation mixin
|
||||
novirtual class AssignGenMixin:
|
||||
@@ -237,22 +238,7 @@ novirtual class AssignGenMixin:
|
||||
# Auto-derived => 64-bit pattern; explicit type T => T's width range.
|
||||
def define_min_width(self, value: int64) -> int:
|
||||
"""Return the minimal storable width (8/16/32/64) for an integer value."""
|
||||
if value < 0:
|
||||
if value >= -128: return 8
|
||||
if value >= -32768: return 16
|
||||
# -2^31: use an arithmetic equivalent to avoid an i32-wrapping literal
|
||||
if value >= -2147483647 - 1: return 32
|
||||
return 64
|
||||
if value <= 255: return 8
|
||||
if value <= 65535: return 16
|
||||
if value <= 2147483647: return 32
|
||||
# 2^31..2^32-1:无符号 32 位位型即可容纳 → 32 位。
|
||||
# 用 (value >> 32) == 0 判断,**不要**写成 uint64(uint32(value)) == value:uint32(...) 之后的
|
||||
# 加宽方向(zext/sext)由 should_zext_widen 按节点无符号性决定,TPC 与 VPC 自举的判定可能不一致;
|
||||
# 一旦判成 sext,uint64(0x80000000) 会变成 0xFFFFFFFF80000000 ≠ value → 误判 64 位 →
|
||||
# `GENERIC_READ | GENERIC_WRITE` 被放大到 i64 → 赋给 32 位目标报隐式窄化。
|
||||
if (value >> 32) == 0: return 32
|
||||
return 64
|
||||
return IntFacts.MinStorableWidth(value)
|
||||
|
||||
# _is_static_int_expr - 该节点是否属于通用求值器一定能折叠的整型表达式(只判定,不求值)
|
||||
def _binop_is_foldable(self, op: int) -> int:
|
||||
|
||||
@@ -10,6 +10,7 @@ import lib.core.Gen.Translator as HT
|
||||
|
||||
import lib.core.Gen.GenTypes as GenTypes
|
||||
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
|
||||
import lib.types.IntFacts as IntFacts
|
||||
|
||||
|
||||
# ExprGenConst - split from ExprGenMixin (stage D): constants / global strings / list literals
|
||||
@@ -64,15 +65,7 @@ novirtual class ExprGenConst(ExprGenBase.ExprGenBase):
|
||||
# 越界编译期 fail-fast(不允许隐式截断)。w==64 时 int64 全域都合法。
|
||||
def _explicit_width_int_const(self, pool: memhub.MemBuddy*, node: ast.AST*, w: int, is_unsigned: int, iv: int64) -> mlirlite.Value*:
|
||||
"""Explicit-width integer literal: strict range check, then a constant of exactly w bits."""
|
||||
ok: int = 1
|
||||
if is_unsigned != 0:
|
||||
if w < 64 and iv < 0: ok = 0
|
||||
elif w <= 62 and iv > (int64(1) << w) - 1: ok = 0
|
||||
else:
|
||||
if w < 64:
|
||||
lo: int64 = -(int64(1) << (w - 1))
|
||||
hi: int64 = (int64(1) << (w - 1)) - 1
|
||||
if iv < lo or iv > hi: ok = 0
|
||||
ok: int = IntFacts.FitsExplicitWidth(w, is_unsigned, iv)
|
||||
if ok == 0:
|
||||
wname: str = "int"
|
||||
if is_unsigned != 0: wname = "uint"
|
||||
|
||||
+6
-20
@@ -12,6 +12,7 @@ import lib.lsp.LspWorkspace as LspWorkspace
|
||||
import lib.lsp.LspTransport as LspTransport
|
||||
import lib.lsp.LspRpc as LspRpc
|
||||
import lib.lsp.LspNavigation as LspNavigation
|
||||
import lib.types.IntFacts as IntFacts
|
||||
|
||||
|
||||
# Longest literal text shown verbatim in a hover (a docstring can be huge).
|
||||
@@ -258,14 +259,7 @@ def _ParseMagnitude(text: str, overflow: int*) -> uint64:
|
||||
|
||||
def _U64Bits(v: uint64) -> int:
|
||||
"""Number of significant bits; zero still needs one bit."""
|
||||
if v == uint64(0):
|
||||
return 1
|
||||
n: int = 0
|
||||
x: uint64 = v
|
||||
while x != uint64(0):
|
||||
n += 1
|
||||
x = x >> uint64(1)
|
||||
return n
|
||||
return IntFacts.MinUnsignedBits(v)
|
||||
|
||||
|
||||
def _TrailingZeros(v: uint64) -> int:
|
||||
@@ -280,16 +274,8 @@ def _TrailingZeros(v: uint64) -> int:
|
||||
|
||||
|
||||
def _PowTwoWidth(bits: int) -> int:
|
||||
"""Smallest power-of-two width >= bits from the language's integer sizes (8..128)."""
|
||||
if bits <= 8:
|
||||
return 8
|
||||
if bits <= 16:
|
||||
return 16
|
||||
if bits <= 32:
|
||||
return 32
|
||||
if bits <= 64:
|
||||
return 64
|
||||
return 128
|
||||
"""Smallest power-of-two width (the language server never recommends arbitrary iN widths)."""
|
||||
return IntFacts.Pow2Width(bits)
|
||||
|
||||
|
||||
def _HexU64Digits(pool: memhub.MemManager*, v: uint64, min_digits: int) -> str:
|
||||
@@ -405,9 +391,9 @@ def _IntMarkdown(pool: memhub.MemManager*, tok: ast.Token*) -> str:
|
||||
decl = _Cat(pool, decl, _U64ToString(pool, uint64(tok.int_width)))
|
||||
fits: int = 0
|
||||
if tok.int_unsigned != 0:
|
||||
fits = 1 if ub <= tok.int_width else 0
|
||||
fits = IntFacts.FitsUnsignedValue(tok.int_width, v)
|
||||
else:
|
||||
fits = 1 if v <= (uint64(1) << uint64(tok.int_width - 1)) - uint64(1) else 0
|
||||
fits = IntFacts.FitsSignedValue(tok.int_width, v, 0)
|
||||
state: str = "fits" if fits != 0 else "overflow"
|
||||
body = _Cat(pool, body, _Cat(pool, _Cat(pool, _Cat(pool,
|
||||
"\ndeclared ", decl), " ("), _Cat(pool, state, ")")))
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
from stdint import *
|
||||
import string
|
||||
|
||||
|
||||
# Shared integer-width facts for the compiler and the language server.
|
||||
# Keep this module free of compiler- or LSP-specific imports: both sides import the same table so
|
||||
# hover numbers, literal range checks and future diagnostics cannot drift apart.
|
||||
#
|
||||
# Literal suffixes (`_iN` / `_uN` / `_intN` / `_uintN`) accept any positive N. Values stored in
|
||||
# the AST are still 64-bit; N only selects the result type width (LLVM/MLIR support iN for any N).
|
||||
|
||||
define INTFACT_MAX_WIDTH = 2147483647
|
||||
|
||||
|
||||
def MinUnsignedBits(v: uint64) -> int:
|
||||
"""Significant bits of an unsigned magnitude; zero still occupies one bit."""
|
||||
if v == uint64(0):
|
||||
return 1
|
||||
n: int = 0
|
||||
x: uint64 = v
|
||||
while x != uint64(0):
|
||||
n += 1
|
||||
x = x >> uint64(1)
|
||||
return n
|
||||
|
||||
|
||||
def MinSignedBits(v: uint64) -> int:
|
||||
"""Bit width needed by a non-negative magnitude as a two's-complement value."""
|
||||
return MinUnsignedBits(v) + 1
|
||||
|
||||
|
||||
def Pow2Width(bits: int) -> int:
|
||||
"""Smallest power-of-two width >= bits, starting at the language's 8-bit minimum.
|
||||
|
||||
The language accepts arbitrary iN/uN types; the language server deliberately only
|
||||
recommends power-of-two storage widths, so this is the single mapping used for display.
|
||||
"""
|
||||
if bits <= 8:
|
||||
return 8
|
||||
w: int = 8
|
||||
while w < bits:
|
||||
w = w * 2
|
||||
return w
|
||||
|
||||
|
||||
def MinStorableWidth(value: int64) -> int:
|
||||
"""Minimal storable width (8/16/32/64) for an automatically typed integer constant."""
|
||||
if value < 0:
|
||||
if value >= -128:
|
||||
return 8
|
||||
if value >= -32768:
|
||||
return 16
|
||||
if value >= -2147483647 - 1:
|
||||
return 32
|
||||
return 64
|
||||
if value <= 255:
|
||||
return 8
|
||||
if value <= 65535:
|
||||
return 16
|
||||
if value <= 2147483647:
|
||||
return 32
|
||||
# 2^31..2^32-1 fit the 32-bit bit pattern. Use `(value >> 32) == 0` here: writing
|
||||
# `uint64(uint32(value)) == value` lets the widening direction (zext/sext) depend on node
|
||||
# signedness, which differs between TPC bootstrap and VPC and can misclassify 0x80000000
|
||||
# as 64-bit (then `GENERIC_READ | GENERIC_WRITE` gets widened to i64 and fails narrowing).
|
||||
if (value >> 32) == 0:
|
||||
return 32
|
||||
return 64
|
||||
|
||||
|
||||
def FitsExplicitWidth(width: int, is_unsigned: int, value: int64) -> int:
|
||||
"""Whether an explicit-width literal fits its suffix width (compiler AST value domain).
|
||||
|
||||
`value` is the int64 bit pattern produced by the lexer; widths above 64 always fit because
|
||||
the language's literal magnitude is limited to 64 bits.
|
||||
"""
|
||||
if width <= 0 or width > INTFACT_MAX_WIDTH:
|
||||
return 0
|
||||
if is_unsigned != 0:
|
||||
if width >= 64:
|
||||
return 1
|
||||
# width < 64: negative bit patterns mean the source magnitude exceeded the width.
|
||||
if value < 0:
|
||||
return 0
|
||||
return 1 if uint64(value) <= (uint64(1) << uint64(width)) - uint64(1) else 0
|
||||
if width >= 64:
|
||||
# i64 covers the whole int64 domain; wider types cover it as well.
|
||||
return 1
|
||||
lo: int64 = -(int64(1) << (width - 1))
|
||||
hi: int64 = (int64(1) << (width - 1)) - int64(1)
|
||||
return 1 if value >= lo and value <= hi else 0
|
||||
|
||||
|
||||
def FitsUnsignedValue(width: int, magnitude: uint64) -> int:
|
||||
"""Magnitude-based unsigned range check (used by the language server)."""
|
||||
if width <= 0:
|
||||
return 0
|
||||
if width >= 64:
|
||||
return 1
|
||||
return 1 if magnitude <= (uint64(1) << uint64(width)) - uint64(1) else 0
|
||||
|
||||
|
||||
def FitsSignedValue(width: int, magnitude: uint64, negative: int) -> int:
|
||||
"""Magnitude-based signed range check (used by the language server)."""
|
||||
if width <= 0:
|
||||
return 0
|
||||
if magnitude == uint64(0):
|
||||
return 1
|
||||
if width > 64:
|
||||
return 1
|
||||
if width == 64:
|
||||
if negative != 0:
|
||||
return 1 if magnitude <= (uint64(1) << uint64(63)) else 0
|
||||
return 1 if magnitude <= (uint64(1) << uint64(63)) - uint64(1) else 0
|
||||
limit: uint64 = uint64(1) << uint64(width - 1)
|
||||
if negative != 0:
|
||||
return 1 if magnitude <= limit else 0
|
||||
return 1 if magnitude <= limit - uint64(1) else 0
|
||||
@@ -0,0 +1,203 @@
|
||||
# 计划:LSP 语义诊断(NameError、位宽存储等)
|
||||
|
||||
状态:待评审;基础设施尚未开工。
|
||||
已完成的前置工作(2026-10-28):
|
||||
|
||||
- lexer 宽度后缀修复:`_u8` / `_i7` / `_uint100` / `_int24` 等现在可被识别,
|
||||
数字分隔符 `_` 只有在后面紧跟合法数字时才被吞掉,宽度 N 不再限制在 1..64。
|
||||
- `_iN/_uN` 负字面量折叠:`-64_i7`、`-128_i8` 按负值做范围检查,不再被当成正的幅值拒绝。
|
||||
- `atoll` / `atof`:支持 `0b`/`0o` 前缀和 `_` 数字分隔符,字面量值不再被解析成 0 或截断。
|
||||
- 新增 `App/lib/types/IntFacts.vp`:编译器与 LSP 共用的宽度/范围/最小二幂宽度表;
|
||||
`ExprGenConst` 的显式宽度检查、`AssignGenMixin.define_min_width`、hover 已改为调用它。
|
||||
- hover:`_u100` 等任意后缀可显示;推荐宽度仍只给二幂位宽(u8/u16/...)。
|
||||
|
||||
尚未包含:任意宽度**类型标注**(`x: i100`)仍走固定类型表;这涉及 TypeGen/TypeSpec/
|
||||
sizeof/ABI,需要单独评估。
|
||||
|
||||
## 1. 现状
|
||||
|
||||
- LSP 目前的诊断只有语法错误:`LspDiagnostics._Parse()` 调用 `ast.parse_ex`,把
|
||||
`ast.get_parse_errors()` 转成 `textDocument/publishDiagnostics` / pull diagnostics。
|
||||
- 编译器已经有成熟的报错,但都在代码生成阶段 fatal,不能直接给 LSP 复用:
|
||||
- 整数宽度:`AssignGenMixin.define_min_width()`(8/16/32/64)、`require_cdefine_storable()`;
|
||||
- 字面量宽度后缀:`ExprGenConst.vp` "integer literal ... does not fit the explicit width";
|
||||
- 常量窄化:`ExprGenBase.vp` "narrowing truncation overflow" / "implicit narrowing is forbidden";
|
||||
- 数组/字符串初始化长度:`AssignGenMixin.vp` 1960/1970;
|
||||
- NameError:未导入模块的调用 `CallGenMixin.vp:253`、static 跨模块、未注册全局变量等。
|
||||
- `LspSymbols.Analyze()` 已经提供作用域、声明、occurrence、import/wildcard、注解详情,
|
||||
hover/跳转/重命名都建立在它上面;语义诊断可以复用,不需要另写一遍解析。
|
||||
- `cif`/`celif`/`celse` 没有独立 AST 节点:parser 把它们 desugar 成普通 `If`,
|
||||
所以 LSP 会把所有分支的声明合并进同一作用域。这直接影响 NameError 的误报策略。
|
||||
- 已知缺陷:数字字面量宽度后缀(`128_u8`/`128_int8`)lexer 实际不识别——
|
||||
`_read_number` 的数字循环先把 `_` 当分隔符吃掉,`_read_width_suffix` 再看时已经没有下划线。
|
||||
编译器里对应的宽度后缀检查因此是死代码。
|
||||
|
||||
## 2. 目标 / 非目标
|
||||
|
||||
目标:
|
||||
|
||||
1. LSP 推送与拉取诊断统一包含:语法、名称解析、位宽/存储三类问题。
|
||||
2. 每条诊断有稳定 `code`、精确 range、严重级别、可选的 relatedInformation / 快速修复。
|
||||
3. 规则与编译器语义一致;不一致的地方必须是有意为之并写进文档。
|
||||
4. 对仓库自身代码(能正常编译)零误报。
|
||||
5. 大文件诊断有明确时间/数量上限,不阻塞编辑。
|
||||
|
||||
已定决策:
|
||||
|
||||
- 字面量宽度后缀 `_iN/_uN/_intN/_uintN` 接受任意正整数 N(LLVM/MLIR 任意位宽),
|
||||
语言层不设 1..64 限制;仅 `int` 溢出时视为非法后缀。AST 值域仍是 64 位。
|
||||
- 编译器与 LSP 共用 `App/lib/types/IntFacts.vp`(宽度/范围/最小二幂宽度/最小可存宽度)。
|
||||
- LSP 展示层只给二幂位宽(u8/u16/.../u128/...),不推荐任意 iN;
|
||||
源里显式写出的 `_u100` 属于用户声明,原样回显。
|
||||
- 诊断级别采用 Pylance 式多模式:`off / basic / standard / strict`,默认 `standard`(普通)。
|
||||
确定规则(字面量越界、数组长度)在 basic/standard/strict 下都是 error;
|
||||
启发式规则(NameError/import)在 basic 下关闭、standard 下 warning、strict 下 error。
|
||||
模式来源:`initializationOptions.typeCheckingMode` 与
|
||||
`workspace/didChangeConfiguration` 的 `settings.viper.typeCheckingMode`(兼容 Pylance 命名)。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不实现完整类型检查器、生命周期/借用检查、泛型推断。
|
||||
- 不在每次击键时调用编译器;LSP 诊断是独立子集,不替代编译错误。
|
||||
- 不把 `Vipeye` 引入服务端。
|
||||
- 不实现大于 64 位字面量数值的大整数解析(N 只决定类型宽度,值仍限 64 位)。
|
||||
|
||||
## 3. 架构
|
||||
|
||||
新增 `ViperC/App/lib/lsp/LspChecks.vp`:
|
||||
|
||||
```
|
||||
LspDiag { sl, sc, el, ec, severity, code, message, related_line, related_file }
|
||||
Check(pool, doc, a, ws, out) -> int # 规则调度;每条规则 append
|
||||
```
|
||||
|
||||
- `LspDiagnostics` 的 `_BuildItems` 改为「语法诊断 + 语义诊断」合并,
|
||||
`publish` 与 pull 共用同一条路径。
|
||||
- 去重键 `(code, start, end)`;总量上限 500;超限追加一条提示并可配置关闭。
|
||||
- 每条诊断带 `code`,例如 `name-undefined` / `literal-width-overflow` / `decl-narrowing` /
|
||||
`array-init-length` / `module-not-found` / `missing-import-member`。
|
||||
- 规则按严重级别:确定结论用 `error`,启发式先用 `warning`(见决策)。
|
||||
- 缓存:按文档 `version` 缓存 `LspAnalysis + 诊断结果`,push/pull 不重复分析。
|
||||
- 预算:规则循环内检查计数与耗时(`os.clock` 或现有计时设施),超预算只返回已完成部分。
|
||||
|
||||
宽度事实表:
|
||||
|
||||
- 纯逻辑模块 `App/lib/Types/IntFacts.vp`(不依赖 lsp/core 任一侧):
|
||||
- 类型名 → `{bits, signed, min, max}`
|
||||
- `min_storable_width(value)`(与 `define_min_width` 对齐:8/16/32/64)
|
||||
- 字面量 → 无符号幅值 + 后缀宽度
|
||||
- 浮点可表示性(与 hover 的 f16/f32/f64 判定共用)
|
||||
- 由编译器和 LSP 共同 import(推荐),或 LSP 先本地实现 + parity 测试(见决策)。
|
||||
- hover 的 `uint/int/float` 行改为调用同一张表,保证 hover 与诊断数字一致。
|
||||
|
||||
## 4. 分阶段
|
||||
|
||||
### P0 诊断基础设施(0.5–1 天)
|
||||
|
||||
- 定义 `LspDiag`、JSON 构造、合并、去重、code/severity/related。
|
||||
- 接入 `LspDiagnostics.Publish/Pull`;语法诊断仍走原路径。
|
||||
- 新增 `probe.py --gate13`:先放一条确定性规则(字面量宽度后缀)做端到端验证。
|
||||
- 验收:gate13 能收到带 code/range 的诊断;gate1–12 不回归。
|
||||
|
||||
### P1 位宽与存储(1–2 天)
|
||||
|
||||
前置(已完成):修 `includes/ast/lexer.vp` 的宽度后缀;新增 `IntFacts` 共用表。
|
||||
|
||||
规则:
|
||||
|
||||
1. `literal-width-overflow`:显式后缀 `_u8/_int8/.../_uint64` 与幅值不符 →
|
||||
error;范围 `[0, 2^n-1]` / `[-2^(n-1), 2^(n-1)-1]`;消息与 `ExprGenConst.vp:79` 对齐。
|
||||
2. `decl-narrowing`:带注解的声明/字段/全局/define 用字面量初始化且越界 → error。
|
||||
只处理内建整数类型与已知别名(`char/uchar/uint/int/long/sizet` 等);typedef、泛型、指针跳过。
|
||||
3. `assign-narrowing`:`x = 字面量`,`x` 解析到带注解符号且越界 → error。
|
||||
4. `neg-literal`:`-1` 形式;只处理一元负号 + 字面量,不做常量折叠。
|
||||
5. `array-init-length`:`[N]T` 字符串初始化 `strlen+1 > N`,或列表元素数 ≠/ > N → error
|
||||
(与编译器消息一致)。
|
||||
6. `float-narrowing`:先核对编译器对 `x: float = 3.5` 是否放行,再决定是否实现;不猜想。
|
||||
7. 64 位以上字面量:lexer 已有溢出诊断,确保不重复上报。
|
||||
|
||||
验收:fixture 覆盖每个规则的正/反例;仓库自身代码零 error。
|
||||
|
||||
### P2 NameError(2–3 天)
|
||||
|
||||
前置:
|
||||
|
||||
- `LspSymbols` 给模块路径 occurrence 打标记(`is_module_path`),避免把模块段当未定义名。
|
||||
- 建立内置名白名单(`int/bool/str/bytes/...`、转换函数、`len/sizeof/defined` 等);
|
||||
优先从编译器内建表生成,避免手抄漂移。
|
||||
- `defined(NAME)` 的参数不算未定义。
|
||||
|
||||
规则:
|
||||
|
||||
1. `name-undefined`:load 上下文的裸标识符,`sym/head` 都为空,且不是内建/类型/泛型参数/
|
||||
模块路径/`defined` 参数 → error(或首版 warning)。
|
||||
2. `unimported-module-call`:`mod.f(...)` 中 `mod` 是工作区已知模块但本文件未 import →
|
||||
error;对齐 `CallGenMixin.vp:253`。
|
||||
3. 快速修复:按工作区索引找同名导出符号,提供「添加 import」;最多 3 条 did-you-mean。
|
||||
|
||||
抑制规则(必须先实现,否则误报会失控):
|
||||
|
||||
- 文件存在无法解析的 `from X import *`:该文件跳过 `name-undefined`。
|
||||
- `cif/celif/celse`:按所有分支的并集解析;任一分支绑定过就不报。
|
||||
- 无法判定的 `cif` 条件:跳过整条链。
|
||||
- 标识符位于属性链(`a.b` 的 `b`)→ 交给成员解析,不报 name-undefined。
|
||||
- 调用目标:编译器允许未知名字退化成外部 C 函数,v1 默认不报裸调用;
|
||||
只报已知模块未导入的调用(见决策 1)。
|
||||
- 泛型参数、`self/cls`、`c.` 内建调用跳过。
|
||||
|
||||
暂不做:use-before-assignment、未使用变量/导入(后续独立规则)。
|
||||
|
||||
### P3 导入/模块诊断(1–2 天)
|
||||
|
||||
- `module-not-found`:`import`/`from` 解析不到文件且无 wildcard 兜底 → warning
|
||||
(工作区 roots 可能不完整,先用 warning;gate10 的 `ghost_lib` 夹具改为预期 warning)。
|
||||
- `missing-import-member`:模块存在但 `from mod import name` 不存在;含 `__init__` 重导出、
|
||||
wildcard 链、`static` 跨模块规则。
|
||||
- `import-cycle`:wildcard 重导出环 → warning(现在的 hop 上限只保证不挂死)。
|
||||
|
||||
### P4 质量与交付(1–2 天)
|
||||
|
||||
- `gate13` 固化:每个 code 一条正例 + 一条反例,断言 code/range/severity/message 关键字。
|
||||
- 误报语料:对 `App/main.vp`、`App/lib/lsp/*.vp`、`includes/_io.vp` 等能编译的文件跑 pull,
|
||||
断言 0 error;脚本化进 gate13。
|
||||
- 性能:2k 行文件 pull p95 ≤ 30 ms;10k 行有预算截断;重复 pull 命中缓存。
|
||||
- 文档:`Tools/lsp/README` 增加诊断 code 表、抑制条件、已知差异。
|
||||
- 可选的 code action:加 import、加宽类型、显式 cast。
|
||||
|
||||
## 5. 风险与对策
|
||||
|
||||
| 风险 | 对策 |
|
||||
| --- | --- |
|
||||
| 误报摧毁信任 | 启发式先 warning;语料零误报门禁;每条规则可单独关闭 |
|
||||
| LSP 与编译器规则漂移 | 共享 `IntFacts`,或 parity fixture 双向对比 |
|
||||
| `cif` 双分支造成 NameError 假阳/假阴 | 分支并集 + 无法判定就跳过 |
|
||||
| wildcard 未知导出 | 有未解析 wildcard 时禁用 name-undefined |
|
||||
| 大文件诊断慢 | 缓存 + 预算 + 上限;只在打开/变更的文档上跑 |
|
||||
| 索引未就绪导致跨文件误报 | 诊断前确保索引可用;解析失败时不产生跨文件结论 |
|
||||
| 编译器允许隐式 extern | 裸调用名默认不报,只报已知模块未导入 |
|
||||
|
||||
## 6. 依赖与顺序
|
||||
|
||||
1. P0 基础设施
|
||||
2. P1 前置:lexer 宽度后缀修复
|
||||
3. P1 位宽规则(与 hover 共用事实表)
|
||||
4. P2 NameError(先白名单/抑制,再规则)
|
||||
5. P3 导入诊断
|
||||
6. P4 语料/性能/文档
|
||||
|
||||
每个阶段结束:`ast_check` → 一次 `--build-self` → 对应 gate + 现有 gate1–12 → 部署 →
|
||||
单独提交。不在同一阶段内反复重建。
|
||||
|
||||
## 7. 需要拍板的点
|
||||
|
||||
已拍板:
|
||||
|
||||
1. 宽度表:编译器与 LSP 共用 `IntFacts`。
|
||||
2. 后缀:任意正整数 N,语言层不限制。
|
||||
3. LSP 位宽展示:只给二幂位宽。
|
||||
4. 诊断级别:Pylance 式 off/basic/standard/strict,默认 standard。
|
||||
|
||||
仍待确认:
|
||||
|
||||
1. NameError 首版是否包含裸函数调用(编译器允许未知调用退化为外部 C 函数,建议不报)。
|
||||
2. `module-not-found` 是否默认 warning(工作区 roots 可能不完整,建议 warning)。
|
||||
@@ -70,6 +70,22 @@ def binary_use() -> int:
|
||||
return 0b1010
|
||||
|
||||
|
||||
def separator_use() -> int:
|
||||
return 1_000
|
||||
|
||||
|
||||
def suffix_use() -> int:
|
||||
return 128_u8
|
||||
|
||||
|
||||
def wide_suffix_use() -> int:
|
||||
return 1_u100
|
||||
|
||||
|
||||
def small_suffix_use() -> int:
|
||||
return 128_u7
|
||||
|
||||
|
||||
def float_use() -> float:
|
||||
return 3.5
|
||||
|
||||
|
||||
@@ -1755,6 +1755,17 @@ def gate10(exe: str) -> int:
|
||||
for piece in ("dec 10", "hex 0xA", "binary 0b1010"):
|
||||
if piece not in bin_hover:
|
||||
raise AssertionError(f"binary hover lacks {piece!r}: {bin_hover}")
|
||||
sep_hover = hover_text(at("return 1_000", 7))
|
||||
if "dec 1000" not in sep_hover:
|
||||
raise AssertionError(f"digit separator changed the value: {sep_hover}")
|
||||
for needle, expected in (
|
||||
("return 128_u8", "declared u8 (fits)"),
|
||||
("return 1_u100", "declared u100 (fits)"),
|
||||
("return 128_u7", "declared u7 (overflow)"),
|
||||
):
|
||||
suffix_hover = hover_text(at(needle, 7))
|
||||
if expected not in suffix_hover:
|
||||
raise AssertionError(f"suffix hover lacks {expected!r}: {suffix_hover}")
|
||||
float_hover = hover_text(at("return 3.5", 7))
|
||||
if "hex 0x400C000000000000" not in float_hover:
|
||||
raise AssertionError(f"float hover has the wrong bit pattern: {float_hover}")
|
||||
|
||||
Reference in new issue
Block a user