fix(vipeye-internal): 拦截 const 指针下标赋值并放行 const 指针迭代
- const 指针的 p[i] = v 下标赋值与 *p = v 解引用赋值共用 viperConstDerefAssignment 诊断,指向只读数据一律报错; 非 const 指针(buf: str)的下标/解引用赋值仍合法 - 迭代判据改认全 Viper 指针(CPtr / str / bytes):新增 viperTypeContainsPointer 并替换 getTypeOfIterator / getTypeOfIterable 里只认 CPtr 的旧写法,path: const str 的 for ch in path: 不再 误报「“CConst”不可迭代 / 未定义 __iter__」;const int 仍不可迭代 - 补 2 个回归用例(下标读写、const 指针可迭代),jest 803 -> 805
This commit is contained in:
1 parent
a7a00a23d5
commit
2a7757e3b7
4 files changed
+135
-10
No files matched your search
@@ -152,6 +152,8 @@ packages/vipeye-internal/src/tests/__viper_static__/
|
||||
packages/vipeye-internal/src/tests/__viper_bracket_array_expr__/
|
||||
packages/vipeye-internal/src/tests/__viper_type_binop_bound__/
|
||||
packages/vipeye-internal/src/tests/__viper_const_ptr_deref__/
|
||||
packages/vipeye-internal/src/tests/__viper_const_ptr_index__/
|
||||
packages/vipeye-internal/src/tests/__viper_const_ptr_iter__/
|
||||
|
||||
# perfCompare.py cached commit bundles
|
||||
/build/perfCompare/
|
||||
|
||||
@@ -3300,10 +3300,8 @@ export function createTypeEvaluator(
|
||||
if (ClassType.isBuiltIn(subtype, ['str', 'bytes'])) {
|
||||
return getBuiltInObject(errorNode, 'char');
|
||||
}
|
||||
// 检查联合类型中是否包含 CPtr
|
||||
const typeContainsCPtr = findSubtype(type, (s) =>
|
||||
isClassInstance(s) && isCPtrType(s)) !== undefined;
|
||||
if (typeContainsCPtr && (
|
||||
// 检查联合类型中是否包含 Viper 指针(CPtr / str / bytes)
|
||||
if (viperTypeContainsPointer(type) && (
|
||||
isCConstType(subtype) || isCStaticType(subtype) || isCVolatileType(subtype) ||
|
||||
isCVoidType(subtype) ||
|
||||
ClassType.isBuiltIn(subtype, 'int')
|
||||
@@ -3453,9 +3451,11 @@ export function createTypeEvaluator(
|
||||
}
|
||||
return UnknownType.create();
|
||||
}
|
||||
const typeContainsCPtr = findSubtype(type, (s) =>
|
||||
isClassInstance(s) && isCPtrType(s)) !== undefined;
|
||||
if (typeContainsCPtr && (
|
||||
// 判据必须认全 Viper 指针(CPtr / str / bytes),不能只认 CPtr:
|
||||
// `path: const str`(AST = `CConst | str`)里的指针成员是 str,
|
||||
// 旧写法 findSubtype(isCPtrType) 匹配不上,CConst 落到末尾
|
||||
// 报「“CConst”不可迭代 / 未定义 __iter__」。
|
||||
if (viperTypeContainsPointer(type) && (
|
||||
isCConstType(subtype) || isCStaticType(subtype) || isCVolatileType(subtype) ||
|
||||
isCVoidType(subtype) ||
|
||||
ClassType.isBuiltIn(subtype, 'int')
|
||||
@@ -4837,6 +4837,23 @@ export function createTypeEvaluator(
|
||||
case ParseNodeType.Index: {
|
||||
const baseTypeResult = getTypeOfExpression(target.d.leftExpr, EvalFlags.IndexBaseDefaults);
|
||||
|
||||
// Viper 扩展:const 指针(如 `lfn: const str` = `CConst | str`)指向只读
|
||||
// 数据 —— 下标**读**允许,下标**赋值**(`lfn[i] = c`)必须报错,
|
||||
// 与解引用赋值(`*lfn = c`)共用同一诊断。
|
||||
if (
|
||||
IsViperDialect(AnalyzerNodeInfo.getFileInfo(target).executionEnvironment.dialect) &&
|
||||
isViperConstPointerType(baseTypeResult.type)
|
||||
) {
|
||||
addDiagnostic(
|
||||
DiagnosticRule.reportGeneralTypeIssues,
|
||||
LocMessage.viperConstDerefAssignment().format({
|
||||
type: printType(baseTypeResult.type),
|
||||
}),
|
||||
target
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
getTypeOfIndexWithBaseType(
|
||||
target,
|
||||
baseTypeResult,
|
||||
@@ -9554,6 +9571,19 @@ export function createTypeEvaluator(
|
||||
return hasConstModifier && hasPointer;
|
||||
}
|
||||
|
||||
// Viper 扩展:联合类型里是否存在「Viper 指针」子类型(CPtr[T] / str / bytes)。
|
||||
// str / bytes 在 Viper 里就是 C 的 i8*(char*),与 CPtr 同为指针;
|
||||
// 迭代 / 解引用等路径据此判断「联合里的 C 修饰符(CConst 等)只是修饰,可跳过」,
|
||||
// 而 `const int`(CConst | int,无指针成员)仍按不可迭代/不可解引用报错。
|
||||
function viperTypeContainsPointer(type: Type): boolean {
|
||||
return (
|
||||
findSubtype(
|
||||
type,
|
||||
(s) => isClassInstance(s) && (isCPtrType(s) || ClassType.isBuiltIn(s, ['str', 'bytes']))
|
||||
) !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
// Viper 扩展:解析 `t.attr.<name>` / `t.attr.<name>(...)` 属性标记 → t.CAttr 实例。
|
||||
//
|
||||
// 形态(与 TransPyC HandlesAssign._TAttrName / TransPyV ClassGenBase._tattr_name
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
* 边界:两侧不是整数的运算(如类名相加)仍须报错。
|
||||
* ② `const` 指针(如 `lfn: const str` = `CConst | str`)指向只读数据:
|
||||
* 解引用**读**允许(`*lfn`、`lfn[0]`);
|
||||
* 解引用**赋值**必须报错(`*lfn = c`);
|
||||
* 非 const 指针(`buf: str`)的 `*buf = c` 仍合法。
|
||||
* 解引用**赋值**必须报错(`*lfn = c`、`lfn[i] = c`);
|
||||
* 非 const 指针(`buf: str`)的 `*buf = c` / `buf[i] = c` 仍合法。
|
||||
* ③ C 修饰符(CConst / CStatic / CVolatile / CVoid)在迭代 / 解引用路径上
|
||||
* **只是修饰**,跳过即可:`path: const str` 可 `for ch in path:` 迭代,
|
||||
* 不再误报「“CConst”不可迭代 / 未定义 __iter__」(判据须认 str / bytes,
|
||||
* 不能只认 CPtr,见 viperTypeContainsPointer)。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperBracketArrayAndConstPtr.test.ts --runInBand
|
||||
*/
|
||||
@@ -206,3 +210,91 @@ def mutable_ok(buf: str) -> None:
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('const 指针:下标读允许、下标赋值报错;非 const 指针下标赋值仍合法', () => {
|
||||
const code = `def index_read(lfn: const str) -> char:
|
||||
return lfn[0]
|
||||
|
||||
def index_write(lfn: const str) -> None:
|
||||
lfn[0] = char(65)
|
||||
|
||||
def mutable_index_ok(buf: str) -> None:
|
||||
buf[0] = char(65)
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(
|
||||
code,
|
||||
'__viper_const_ptr_index__'
|
||||
);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== const 指针下标诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line}:${d.col} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const constWriteDiags = diagnostics.filter((d) => d.message.includes('const 指针的解引用赋值'));
|
||||
assert.strictEqual(
|
||||
constWriteDiags.length,
|
||||
1,
|
||||
`只应报 1 处 const 下标赋值错误(lfn[0] = ...):${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
// 报错行应为 `lfn[0] = char(65)` 所在行(第 5 行)。
|
||||
assert.strictEqual(
|
||||
constWriteDiags[0].line,
|
||||
5,
|
||||
`const 下标赋值错误应落在第 5 行:${JSON.stringify(constWriteDiags)}`
|
||||
);
|
||||
|
||||
const otherErrors = diagnostics.filter(
|
||||
(d) => d.category === 0 && !d.message.includes('const 指针的解引用赋值')
|
||||
);
|
||||
assert.strictEqual(
|
||||
otherErrors.length,
|
||||
0,
|
||||
`下标读 / 非 const 指针下标赋值不应报错:${JSON.stringify(otherErrors)}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('const 指针可迭代:for ch in path 不报「不可迭代 / 未定义 __iter__」', () => {
|
||||
const code = `import t
|
||||
|
||||
def walk(path: const str) -> int:
|
||||
count: int = 0
|
||||
for ch in path:
|
||||
count = count + 1
|
||||
return count
|
||||
|
||||
def walk_parts(parts: const t.CPtr) -> int:
|
||||
total: int = 0
|
||||
for p in parts:
|
||||
total = total + 1
|
||||
return total
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(
|
||||
code,
|
||||
'__viper_const_ptr_iter__'
|
||||
);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== const 指针迭代诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line}:${d.col} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const notIterableDiags = diagnostics.filter(
|
||||
(d) => d.message.includes('不可迭代') || d.message.includes('__iter__')
|
||||
);
|
||||
assert.strictEqual(
|
||||
notIterableDiags.length,
|
||||
0,
|
||||
`const 指针(const str 等)应可迭代,不应报不可迭代:${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -32,7 +32,8 @@
|
||||
- The **`static` modifier** is now supported (LSP side only; TransPyC/TransPyV are unchanged). It is a *soft* keyword: `static def f(...)` / `static fn f(...)` marks a function (recorded on `FunctionNode.d.viperModifier`, exactly like `virtual`) and `static x: T = v` marks a variable (the annotation is rewritten to `t.CStatic | T`, the same tree TransPyC's `_parse_type_ann` builds by inserting `t.CStatic` at the left of the union, so `static SECRET: int` renders as `static int`). Anywhere else `static` stays an ordinary identifier — `static = 1`, `static: int`, `static(x)`, `def f(static: int)` and `self.static` all parse as before. Its meaning is C's **internal linkage**: a static symbol **cannot be used across modules**. Referencing one from another module is reported as `'lib.helper' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)` for both `mod.name` / `mod.fn()` (checked in the module member-access path) and `from mod import name` (checked on the import statement), while same-module references are allowed — the decision is made per declaration file, so a self-import is not flagged either. `static` is also offered by completion and highlighted as a modifier (`storage.modifier.viper`, matched only in front of `def`-family or `name :`)
|
||||
- The `f` prefix of an f-string is now highlighted as `storage.type.string.viper` (blue in the default dark theme) instead of being left unstyled. The rule matches the `r` / `u` / `b` / `f` prefix combination immediately preceding a quote, so it covers `f"..."`, `f'''...'''` and mixed spellings like `rf"..."`; a bare `r`/`f`/`b` identifier not followed by a quote is unaffected, and the string body keeps the usual `string.quoted.*` scope
|
||||
- The **size of a fixed-length array type may be any integer constant expression** now: `part_types: [MAX_VOLUMES * MAX_PARTITIONS]uint8`, `part_lit: [MAX_VOLUMES * 2]uint8`, `part_left: [MAX_VOLUMES - 1]uint8` and `part_shift: [1 << 3]uint8` all parse and check cleanly. Two bugs made this fail before: the parser parsed the `[SIZE]` part while still flagged as a type annotation, so `_parseArithmeticTerm` deliberately stopped at the `*` (it is the C pointer suffix marker there) — the bracket was reported as "“[" 未关闭" and the leftover `* MAX_PARTITIONS]uint8` as "类型表达式中不允许使用解包运算符"; and the type evaluator then evaluated that arithmetic with `EvalFlags.TypeExpression`, reporting "类型表达式中不允许使用二元运算符". The size is now parsed as the value expression it is (the flag is cleared for its duration), and the evaluator allows arithmetic/bitwise operators in a type expression when **both operands are Viper integers** (the case the enclosing comment already described, e.g. `t.CArray[T, MAX_ORDER + 1]`); non-integer operands (`C1 + C2`) are still reported
|
||||
- **`const` pointers are read-only for dereference assignment**: `lfn: const str` (`CConst | str`, i.e. `const char *`) now allows dereferencing — `*lfn` and `lfn[0]` evaluate to `char` — but writing through the dereference (`*lfn = char(65)`) is reported as "不能给 const 指针的解引用赋值("const str" 指向的数据为只读)". Previously the dereference itself was rejected with a bogus "类型"const str"不支持运算符"*"" (the `CConst` modifier in the union was mistaken for a non-pointer operand), and there was no read-only check at all. A non-`const` pointer (`buf: str`) still accepts `*buf = char(65)`
|
||||
- **`const` pointers are read-only for dereference assignment**: `lfn: const str` (`CConst | str`, i.e. `const char *`) now allows dereferencing — `*lfn` and `lfn[0]` evaluate to `char` — but writing through the dereference is reported as "不能给 const 指针的解引用赋值("const str" 指向的数据为只读)". This covers **both** spellings, `*lfn = char(65)` (dereference) and `lfn[0] = char(65)` (**subscript assignment**), which share the same diagnostic. Previously the dereference itself was rejected with a bogus "类型"const str"不支持运算符"*"" (the `CConst` modifier in the union was mistaken for a non-pointer operand), and there was no read-only check at all. A non-`const` pointer (`buf: str`) still accepts `*buf = char(65)` and `buf[0] = char(65)`
|
||||
- C type descriptors are **skipped** when checking iterability, so a `const` pointer can be iterated: `for ch in path:` with `path: const str` (and `const t.CPtr`) no longer reports "“CConst”不可迭代 / 未定义“__iter__”方法". The predicate now recognizes every Viper pointer (CPtr, `str`, `bytes`) rather than only `CPtr`, which is why `const str` had been missed — `CConst` fell through to the "not iterable" path
|
||||
|
||||
## 1.1.425
|
||||
|
||||
|
||||
Reference in new issue
Block a user