fix(vipeye-internal): REnum 变体指针与根指针互转放行(修复 llvmlite 误判)
指针隐式转换收紧后,「pointee 必须兼容」把 REnum 变体指针 rawptr[LLVMType.Ptr] 判为与 rawptr[LLVMType] 不兼容,导致 llvmlite 的 build_load(builder, ty: rawptr[LLVMType], ...) 误报「无法将 rawptr[Ptr] 类型的参数分配给类型为 rawptr[LLVMType] 的参数 ty」。 成因:变体类(class LLVMType(t.REnum) 内的 class Ptr / class Int ...)是 **嵌套类**而非根类的子类,isDerivedFrom 判不出关系;而 match 的变体模式收窄 (patternMatching.narrowTypeBasedOnClassPattern)会把 CPtr[LLVMType] 收窄成 CPtr[LLVMType.Ptr],CallGenCall.vp 里的 lb_inner 正是这样被收窄后传回根指针形参。 TransPyV 对二者只 emit bitcast(tagged-union 负载地址 ↔ 联合地址),故放行同一 REnum 家族内的指针双向互转:新增 isViperREnumFamilyPointee(fullName 前缀嵌套 判定 + isViperREnumRootClass 判 mro 含 .REnum),与收窄判据同口径。 跨变体(Ptr* → Int*)与 REnum ↔ 无关结构体仍拒绝。 新增 viperREnumPointer.test.ts 2 用例:嵌套 match 收窄形状 0 报错;转换矩阵 断言报错行恰为 [32,36,37]。jest 820 → 822。
This commit is contained in:
1 parent
f31ff4904c
commit
bc5bd3a425
4 files changed
+278
No files matched your search
@@ -164,6 +164,8 @@ packages/vipeye-internal/src/tests/__viper_variadic_noimport__/
|
||||
packages/vipeye-internal/src/tests/__viper_carray_element__/
|
||||
packages/vipeye-internal/src/tests/__viper_bracket_array_no_import_t__/
|
||||
packages/vipeye-internal/src/tests/__viper_export_extern__/
|
||||
packages/vipeye-internal/src/tests/__viper_renum_pointer__/
|
||||
packages/vipeye-internal/src/tests/__viper_renum_pointer_matrix__/
|
||||
|
||||
# perfCompare.py cached commit bundles
|
||||
/build/perfCompare/
|
||||
|
||||
@@ -3630,12 +3630,50 @@ function normalizeViperPointeeType(type: Type): Type {
|
||||
return core;
|
||||
}
|
||||
|
||||
// Viper 扩展:两个 pointee 是否属于同一个 REnum(t.REnum tagged-union)家族——
|
||||
// 其一是继承自 t.REnum 的**根类**,另一个是声明时**嵌套**在根类 suite 里的
|
||||
// **变体类**(如 class LLVMType 内的 class Ptr / Int / Struct)。
|
||||
//
|
||||
// 变体类不是普通子类(不继承根类),故 isDerivedFrom 判不出来;但 TransPyV 对
|
||||
// 「变体指针 ↔ 根指针」只 emit bitcast(tagged-union 的负载地址与联合地址),
|
||||
// 两侧可自由互转。此外 match 的变体模式收窄会把 CPtr[LLVMType] 收窄成
|
||||
// CPtr[LLVMType.Ptr](见 patternMatching.narrowTypeBasedOnClassPattern),
|
||||
// 若不认这一家族关系,收窄后的指针传回 rawptr[LLVMType] 形参会误报。
|
||||
//
|
||||
// 判定用 fullName 前缀('LLVMType.Ptr' ⊂ 'LLVMType'),与 patternMatching 的
|
||||
// 收窄判定口径保持一致;同级的不同变体(Ptr vs Int)互不为前缀,仍被拒绝。
|
||||
function isViperREnumFamilyPointee(firstPointee: Type, secondPointee: Type): boolean {
|
||||
if (!isClass(firstPointee) || !isClass(secondPointee)) {
|
||||
return false;
|
||||
}
|
||||
const firstFullName = firstPointee.shared.fullName;
|
||||
const secondFullName = secondPointee.shared.fullName;
|
||||
if (!firstFullName || !secondFullName) {
|
||||
return false;
|
||||
}
|
||||
if (firstFullName.startsWith(secondFullName + '.')) {
|
||||
return isViperREnumRootClass(secondPointee);
|
||||
}
|
||||
if (secondFullName.startsWith(firstFullName + '.')) {
|
||||
return isViperREnumRootClass(firstPointee);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Viper 扩展:类是否继承自 t.REnum(REnum tagged-union 根类)。
|
||||
function isViperREnumRootClass(classType: ClassType): boolean {
|
||||
return classType.shared.mro.some(
|
||||
(baseClass) => isClass(baseClass) && !!baseClass.shared.fullName?.endsWith('.REnum')
|
||||
);
|
||||
}
|
||||
|
||||
// Viper 扩展:两个 CPtr 之间是否允许隐式转换(指针 → 指针)。
|
||||
// 规则(2026-09-28 收紧,原先「同级即无条件允许」):
|
||||
// 1. 级数必须相同(getCPtrLevel)——不同级一律拒绝;
|
||||
// 2. 任一侧 pointee 是 void*(CVoid)或未定(rawptr = CPtr[未特化],其实参是
|
||||
// 类自身的 TypeVar)→ 允许(void* 通用指针语义,malloc 等场景依赖它);
|
||||
// 3. pointee 规范化后完全相同 → 允许;
|
||||
// 3.5. 同一 REnum 家族(根 ↔ 变体,见 isViperREnumFamilyPointee)→ 允许;
|
||||
// 4. 两侧 pointee 都是数值标量(基本类型)→ 按 T 的隐式转换规则
|
||||
// (整数/浮点全方向、整数→浮点需位宽≥、浮点→整数禁止);
|
||||
// 5. 结构体(非基本类型):仅允许「源类派生自目标类」的**上转型**
|
||||
@@ -3671,6 +3709,10 @@ function isViperCPtrPointeeAssignable(destPtr: ClassType, srcPtr: ClassType): bo
|
||||
if (isTypeSame(destPointee, srcPointee)) {
|
||||
return true;
|
||||
}
|
||||
// REnum(t.REnum tagged-union)家族:变体指针 ↔ 根指针 双向允许。
|
||||
if (isViperREnumFamilyPointee(destPointee, srcPointee)) {
|
||||
return true;
|
||||
}
|
||||
// 仅「基本类型(数值标量)」之间按 T 的隐式转换规则放行。
|
||||
if (isViperNumericScalarPointee(destPointee) && isViperNumericScalarPointee(srcPointee)) {
|
||||
return canImplicitlyConvertCType(srcPointee, destPointee);
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* viperREnumPointer.test.ts
|
||||
*
|
||||
* 验证 REnum(t.REnum tagged-union)指针的隐式转换口径(2026-09-28 修复)。
|
||||
*
|
||||
* 背景:指针隐式转换收紧为「pointee 必须兼容」后,`rawptr[LLVMType.Ptr]`
|
||||
* (REnum 变体指针)与 `rawptr[LLVMType]`(REnum 根指针)被判为不兼容,
|
||||
* 导致 llvmlite 的真实写法误报:
|
||||
* - includes/llvmlite/__builder.vp:
|
||||
* def build_load(builder, ty: rawptr[LLVMType], ptr, is_volatile=0) -> rawptr[Value]
|
||||
* - TransPyV/App/lib/core/Gen/Call/CallGenCall.vp 的嵌套 match:
|
||||
* case llvmlite.LLVMType.Ptr(lb_inner):
|
||||
* match lb_inner:
|
||||
* case llvmlite.LLVMType.Ptr(lb_inner2):
|
||||
* lb_obj = llvmlite.build_load(builder, lb_inner, lb_obj)
|
||||
* 内层 match 把 `lb_inner: rawptr[LLVMType]` 收窄成 `rawptr[Ptr]`
|
||||
* (见 patternMatching.narrowTypeBasedOnClassPattern 的 Viper 扩展),
|
||||
* 再传回 `rawptr[LLVMType]` 形参即误报。
|
||||
*
|
||||
* 规则:同一 REnum 家族(根类 ↔ 其 suite 内声明的变体类)之间的指针互转允许
|
||||
* (TransPyV 只 emit bitcast;判定用 fullName 前缀,与 match 收窄口径一致);
|
||||
* 不同变体之间、REnum 与无关结构体之间仍拒绝。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperREnumPointer.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgramForCode(code: string, dirName: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
|
||||
const fs = require('fs');
|
||||
const testDir = path.join(__dirname, dirName);
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'main.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
return { program, configOptions, uri, serviceProvider, testDir };
|
||||
}
|
||||
|
||||
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any) {
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
assert(sourceFile, 'source file not found');
|
||||
return (sourceFile.getDiagnostics(configOptions) || []).map((diag: any) => ({
|
||||
category: diag.category,
|
||||
line: diag.range ? diag.range.start.line + 1 : -1,
|
||||
message: diag.message as string,
|
||||
}));
|
||||
}
|
||||
|
||||
/** 实际报错(category 0=Error / 1=Warning)所在行集合。 */
|
||||
function hardErrorLines(program: Program, configOptions: ConfigOptions, uri: any): number[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
getDiagnostics(program, configOptions, uri)
|
||||
.filter((d) => d.category === 0 || d.category === 1)
|
||||
.map((d) => d.line)
|
||||
)
|
||||
).sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
test('REnum 变体指针收窄后传回根指针形参不再误报(llvmlite/CallGenCall 形状)', () => {
|
||||
const code = `import t
|
||||
|
||||
class IRBuilder:
|
||||
pass
|
||||
|
||||
class LLVMType(t.REnum):
|
||||
class Ptr:
|
||||
Pointee: rawptr[LLVMType]
|
||||
|
||||
class Int:
|
||||
Bits: int
|
||||
|
||||
class Value:
|
||||
Ty: rawptr[LLVMType]
|
||||
|
||||
def build_load(builder: rawptr[IRBuilder], ty: rawptr[LLVMType], ptr: rawptr[Value]) -> rawptr[Value]:
|
||||
return ptr
|
||||
|
||||
def f(builder: rawptr[IRBuilder], obj: rawptr[Value]) -> int:
|
||||
lb_obj: rawptr[Value] = obj
|
||||
if lb_obj is not None and lb_obj.Ty is not None:
|
||||
match lb_obj.Ty:
|
||||
case LLVMType.Ptr(lb_inner):
|
||||
match lb_inner:
|
||||
case LLVMType.Ptr(lb_inner2):
|
||||
lb_obj = build_load(builder, lb_inner, lb_obj)
|
||||
case _:
|
||||
pass
|
||||
case _:
|
||||
pass
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, uri, testDir, serviceProvider } = buildProgramForCode(
|
||||
code,
|
||||
'__viper_renum_pointer__'
|
||||
);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== REnum 家族指针诊断 ===');
|
||||
diagnostics
|
||||
.filter((d) => d.category === 0 || d.category === 1)
|
||||
.forEach((d) => console.log(` L${d.line} [cat=${d.category}] ${d.message}`));
|
||||
|
||||
const lines = hardErrorLines(program, configOptions, uri);
|
||||
console.log('=== 报错行 ===');
|
||||
lines.forEach((l) => console.log(` L${l}`));
|
||||
assert.deepStrictEqual(
|
||||
lines,
|
||||
[],
|
||||
`REnum 变体指针(收窄为 rawptr[Ptr])传回 rawptr[LLVMType] 形参不应报错:${JSON.stringify(
|
||||
diagnostics.filter((d) => d.category === 0 || d.category === 1)
|
||||
)}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('REnum 指针转换矩阵:家族内互转允许,跨变体与无关结构体拒绝', () => {
|
||||
const code = `import t
|
||||
|
||||
class LLVMType(t.REnum):
|
||||
class Ptr:
|
||||
Pointee: rawptr[LLVMType]
|
||||
|
||||
class Int:
|
||||
Bits: int
|
||||
|
||||
class Other:
|
||||
w: int
|
||||
|
||||
def take_root(p: rawptr[LLVMType]) -> None:
|
||||
pass
|
||||
|
||||
def take_ptr_variant(p: rawptr[LLVMType.Ptr]) -> None:
|
||||
pass
|
||||
|
||||
def take_int_variant(p: rawptr[LLVMType.Int]) -> None:
|
||||
pass
|
||||
|
||||
def take_other(p: rawptr[Other]) -> None:
|
||||
pass
|
||||
|
||||
def variant_to_root(p: rawptr[LLVMType.Ptr]) -> None:
|
||||
take_root(p)
|
||||
|
||||
def root_to_variant(r: rawptr[LLVMType]) -> None:
|
||||
take_ptr_variant(r)
|
||||
|
||||
def variant_mismatch(p: rawptr[LLVMType.Ptr], i: rawptr[LLVMType.Int]) -> None:
|
||||
take_int_variant(p)
|
||||
take_root(i)
|
||||
|
||||
def unrelated(p: rawptr[LLVMType.Ptr], o: rawptr[Other]) -> None:
|
||||
take_other(p)
|
||||
take_root(o)
|
||||
`;
|
||||
const { program, configOptions, uri, testDir, serviceProvider } = buildProgramForCode(
|
||||
code,
|
||||
'__viper_renum_pointer_matrix__'
|
||||
);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== REnum 指针矩阵诊断 ===');
|
||||
diagnostics
|
||||
.filter((d) => d.category === 0 || d.category === 1)
|
||||
.forEach((d) => console.log(` L${d.line} [cat=${d.category}] ${d.message}`));
|
||||
|
||||
// 允许:L26(变体 → 根)、L29(根 → 变体)、L33(Int 变体 → 根)
|
||||
// 拒绝:L32(Ptr 变体 → Int 变体,跨变体)
|
||||
// L36(REnum 变体 → 无关结构体 Other)
|
||||
// L37(无关结构体 Other → REnum 根)
|
||||
const lines = hardErrorLines(program, configOptions, uri);
|
||||
console.log('=== 报错行 ===');
|
||||
lines.forEach((l) => console.log(` L${l}`));
|
||||
assert.deepStrictEqual(
|
||||
lines,
|
||||
[32, 36, 37],
|
||||
`应且仅应报 L32(跨变体)、L36/L37(与无关结构体互转):${JSON.stringify(
|
||||
diagnostics.filter((d) => d.category === 0 || d.category === 1)
|
||||
)}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -17,6 +17,7 @@
|
||||
- Attribute names inside `__attribute__(...)` are highlighted **structurally**, so a name that the compiler supports but that isn't declared in the `attr` class still colours like the declared ones. `stackrealign` (supported by TransPyV's code generator — `FuncGenBase.vp` maps it to the LLVM `"stackrealign"` attribute — and used by ViperOS `VKernel/Kernel/main.vp`) is declared in neither `t.py` nor the `t.vpi` stub, so `t.attr.stackrealign` used to be unresolvable: the name got no semantic token (rendered unhighlighted) and the file was additionally flagged with "无法访问类"type[attr]"的属性"stackrealign"". The parser now marks the synthesized attribute-name node, the semantic token provider colours it as `function` (exactly like a declared attribute such as `section` / `aligned` / `naked`), and the member-access diagnostic is suppressed for these names — the compiler silently drops unknown attributes rather than rejecting them, so reporting them was a false positive. Declared names keep resolving through `t.attr` as before, so hover and jump-to-definition still work for them
|
||||
- `__attribute__(...)` **completion and hover** now list attribute names that the compiler supports but that the `attr` class doesn't declare, instead of only mirroring `t.attr`. `stackrealign` is offered by typing inside the parentheses and appears in the hover list of available names; the list is a single shared union (`viperAttributeUtils.getViperAttributeNames`) of `t.attr`'s declared names plus these extra names, so completion and hover cannot drift apart. Declared names keep coming from the stub symbol table (so they still show their signature and support jump-to-definition)
|
||||
- Pointer-to-pointer implicit conversion is now **strict** instead of "same level is always allowed". A pointer converts implicitly to another pointer only when the **level** matches **and** the pointee is compatible: either side being `void*` / undetermined (`rawptr`) always allows it, identical pointees are allowed, two basic (numeric scalar) pointees follow the very same rules as the values themselves (`uint32* → uint64*` and `uint32* → uint16*` are allowed because integers convert in every direction; `float* → int*` is still reported), and for **structs** only an **upcast** is allowed (`Sub* → Node*`; `memhub` / `requests` depend on `MemBuddy* → MemManager*`), while two unrelated structs (`Node* → Other*`), a downcast, or a struct↔basic mix (`Node* → uint64*`) are rejected. Single-level pointers also convert to and from `void*` (`t.CVoid`) in both directions, while multi-level ones still don't (`Node** → void*` is reported). `&x` / `*x` already produced the right types (`&x` is `T*` for `x: T`, `T**` for `x: T*`; `*x` is `T` for `x: T*`) and now take part in these checks too — `a(&x)` with `x: Node*` (a `Node**` into a `void*` parameter) is reported as before, and so is `a(&x)` where the parameter is a single-level pointer
|
||||
- Pointers to the **variants of a `t.REnum`** are now compatible with pointers to the REnum root (and vice versa), so `rawptr[LLVMType.Ptr]` converts to `rawptr[LLVMType]` instead of being reported as "无法将"rawptr[Ptr]"类型的参数分配给函数"build_load"中类型为"rawptr[LLVMType]"的参数"ty"". The variant classes are nested inside the root class's suite (`class LLVMType(t.REnum): class Ptr: ... / class Int: ...`) rather than derived from it, so `isDerivedFrom` could not see the relation — yet TransPyV only emits a bitcast between the two addresses, and `match`-variant narrowing already rewrites `CPtr[LLVMType]` into `CPtr[LLVMType.Ptr]` (see `narrowTypeBasedOnClassPattern`), which is exactly what llvmlite does: `CallGenCall.vp` narrows `lb_inner: rawptr[LLVMType]` through a nested `case llvmlite.LLVMType.Ptr(lb_inner2)` and passes it straight back to `build_load(builder, ty: rawptr[LLVMType], ...)`. The relation is detected by full-name nesting (`'LLVMType.Ptr' ⊂ 'LLVMType'`, the same criterion the narrowing uses) together with the root's MRO containing `REnum`; two different variants (`Ptr* → Int*`) and a mix with an unrelated struct are still reported
|
||||
- Assigning a **plain function to a method slot of a `virtual class`** is now accepted: `serial0_file.open = serial0_open` (ViperOS `devfs.vp`, where `devfs_file` is a `virtual class` and `serial0_open(self: rawptr, path: const str, flags: int) -> int` is a module-level function) used to be reported as "无法为类"devfs_file"的属性"open"赋值 / 参数名称不匹配: "path" versus "self" / 额外参数 "flags"". Such an assignment writes a **vtable slot**, and the compiler (TPC `HandlesAssign` / TransPyV `CallGenVt._write_vtable_slot`) just bitcasts the function to `i8*` and stores it — it never validates the signature or `self` — whereas the language server compared the *bound* method type (with `self` already stripped), so the explicitly written `self` looked like a spurious extra parameter. The check now follows the vtable semantics: the class must be a `virtual class` (`virtual class X` or `@t.CVTable class X`), the member must be a method declared in it, the assigned function's `self` must be a generic pointer (`rawptr` / `t.CPtr`) or a pointer to that class itself or one of its base classes, and every remaining parameter must match by name and type with an identical return type (`*args` / `**kwargs` are not compared, so they may be present on either side). Everything else — a non-virtual class, `self` that isn't such a pointer, or a parameter/return mismatch — is still reported
|
||||
|
||||
## 1.1.426
|
||||
|
||||
Reference in new issue
Block a user