feat(vipeye-internal): Viper 1.1.426 语法/语义/补全增强
把 TransPyC 现行语法与 Viper 的类型组合语义补齐到 LSP 侧(编译器 TPC/TPV 不改动): - 类型描述符:`const` / `volatile` 可前缀任意类型注解并成为硬关键字(仅注解前缀 合法,`const = 1` / `a: const` 报语法错误),解析层脱糖为等价的 `CConst | T` / `CVolatile | T`,与手写 TypeSet 完全同形;`const T*` 得 `CConst | CPtr[T]`; `T*` / `&x` 不再要求 import t(回退 builtins 的 rawptr);描述符按「前缀限定符」 渲染(`const int` / `const volatile int` / `const rawptr[int]`)而不泄漏为联合成员 - 新增 `inline while` / `inline for`;`const` / `volatile` / `inline` 高亮为修饰符 - 新增内建类型名 uint / uchar,并把 rawptr / sizet / int8..uint64 / uintfast* / uintleast* 等内建到 builtins.vpi,任意 Viper 工程无需配置 includes - 新增 `virtual` / `novirtual` 声明修饰符(等价 @t.CVTable / @t.NoVTable), 含放置位置校验,并并入补全与高亮 - `(T**)x` 两级指针强转修复:指针类型后缀统一按类型表达式求值,不再被当成解引用 - 裸内置内联汇编 / LLVM IR:`__asm__` / `asminp` / `asmout` / `__llvmir__` / `irinp` / `irout` / `ASM_DESCR`,与 c.* / t.ASM_DESCR 写法等价 - `X | t.CPtr` 在 X 本身是指针时再包一层(HANDLE | t.CPtr = void**), 修复 CreatePipe 一类 LPHANDLE 的「跨级指针隐式转换不允许」误报 - 补全并入 Viper 专有关键字(define / const / volatile / typedef / inline / export / extern / virtual / novirtual / enum / rnum / union / struct / fn / fun / func / function) - `T*(x)`:裸模块名 + 模块内同名类归一为 CPtr[bootinfo];并按 TPC `_AtPtrSuffixCall` 的判据把「类型原子 + 紧贴星串 + 紧贴 (」解析为调用,使 `uint32*(info.vendor_id)[0] = res.ebx` 成为合法赋值目标 - 新增「重复声明」检查:模块级已有同名运行期变量时,函数内再用类型注解声明同名 变量无条件报 TypeError(与 TPV AssignGenMixin 同口径);排除 CDefine 宏、CTypedef 别名、同名函数/类与类体成员。合法写法只剩 `global x` + 无注解赋值,或换名 - 支持 `__attribute__`:装饰器 `@__attribute__(...)` 脱糖为 `@c.Attribute(t.attr.X, ...)`;注解修饰器 `a: __attribute__(...) const int` 脱糖为 `t.attr.X | (CConst | int)`;注解联合中的 `t.attr.*` 标记求值为 t.CAttr 修饰符(运算/下标/赋值/展示均按被修饰的基础类型处理);值位置 `volatile f()` / `volatile 结构体()` 前缀;`__a` 补全、括号内属性名补全与悬停、 TextMate 高亮 - 注解位置小写 `callable[[A, B], R]` 等价 t.Callable:parser 仅在类型注解位置把裸名 `callable` 脱糖为 `Callable`,builtins.vpi 内建 `Callable` 别名(无需 import t); 值位置 `callable(x)` 仍是内置函数 - 版本号提升至 1.1.426 并更新 CHANGELOG;新增 6 个回归测试文件、更新 4 个, 全量 64 套件 / 794 用例全绿(另有 1 套件 8 用例为既有 skip)
This commit is contained in:
1 parent
b98102b78f
commit
baec16e540
37 files changed
+3719
-77
No files matched your search
@@ -138,6 +138,14 @@ packages/vipeye-internal/src/tests/benchmarks/.generated/
|
||||
# 测试运行生成的产物(每次跑 jest 都会被重写,不入库)
|
||||
packages/vipeye-internal/src/tests/__ty_narrow_dump.txt
|
||||
packages/vipeye-internal/src/tests/__unary_ptr__/
|
||||
packages/vipeye-internal/src/tests/__global_redeclare__/
|
||||
packages/vipeye-internal/src/tests/__viper_attribute__/
|
||||
packages/vipeye-internal/src/tests/__viper_attribute_completion__/
|
||||
packages/vipeye-internal/src/tests/__viper_attribute_hover__/
|
||||
packages/vipeye-internal/src/tests/__viper_attribute_prefix__/
|
||||
packages/vipeye-internal/src/tests/__viper_volatile__/
|
||||
packages/vipeye-internal/src/tests/__viper_volatile_bad__/
|
||||
packages/vipeye-internal/src/tests/__viper_callable__/
|
||||
|
||||
# perfCompare.py cached commit bundles
|
||||
/build/perfCompare/
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vipeye-internal",
|
||||
"version": "1.1.425",
|
||||
"version": "1.1.426",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vipeye-internal",
|
||||
"version": "1.1.425",
|
||||
"version": "1.1.426",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@yarnpkg/fslib": "2.10.4",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "vipeye-internal",
|
||||
"displayName": "vipeye",
|
||||
"description": "Type checker for the Viper language",
|
||||
"version": "1.1.425",
|
||||
"version": "1.1.426",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"files": [
|
||||
|
||||
@@ -161,6 +161,7 @@ import {
|
||||
Variance,
|
||||
combineTypes,
|
||||
isAnyOrUnknown,
|
||||
isCDefineType,
|
||||
isClass,
|
||||
isClassInstance,
|
||||
isFunction,
|
||||
@@ -1761,6 +1762,11 @@ export class Checker extends ParseTreeWalker {
|
||||
|
||||
override visitTypeAnnotation(node: TypeAnnotationNode): boolean {
|
||||
this._evaluator.getType(node.d.annotation);
|
||||
|
||||
// Viper 扩展:函数内用类型注解重新声明模块级同名变量(`x: T` / `x: T = v`)
|
||||
// → 「重复声明」(TypeError),无论是否声明 global/nonlocal。
|
||||
this._validateViperGlobalRedeclaration(node);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2218,6 +2224,84 @@ export class Checker extends ParseTreeWalker {
|
||||
);
|
||||
}
|
||||
|
||||
// Viper 扩展:模块级已有同名**运行期变量**时,函数内不得再用**类型注解**声明同名变量。
|
||||
// 与 TPV(TransPyV/App/lib/core/Gen/AssignGenMixin.vp 的「重复声明」检测)同口径,
|
||||
// **无条件**失败(判据不含"类型是否一致":注解类型即便与全局完全相同也是重复声明):
|
||||
// ① 无 global/nonlocal 声明 → 会把全局遮蔽成局部,属重复声明;
|
||||
// ② 有 global/nonlocal 声明 → 注解与既有全局声明重复,属重复声明。
|
||||
// 合法的两种写法:函数内 `global x` + **无注解**赋值 `x = v`;或换用不重名的局部名。
|
||||
// 范围仅函数内——模块级自身的重复注解、类体成员声明都不检查(模块级由全局变量表登记,
|
||||
// 本就不构成"遮蔽")。
|
||||
private _validateViperGlobalRedeclaration(node: TypeAnnotationNode) {
|
||||
if (!IsViperDialect(this._fileInfo.executionEnvironment.dialect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nameNode = node.d.valueExpr;
|
||||
if (nameNode.nodeType !== ParseNodeType.Name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 仅「变量声明语句」:`x: T`(语句本身)与 `x: T = v`(赋值的左值)。
|
||||
const parent = node.parent;
|
||||
if (parent?.nodeType !== ParseNodeType.Assignment && parent?.nodeType !== ParseNodeType.StatementList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scope = getScopeForNode(node);
|
||||
if (scope?.type !== ScopeType.Function) {
|
||||
return;
|
||||
}
|
||||
|
||||
const globalScope = scope.getGlobalScope().scope;
|
||||
if (globalScope.type !== ScopeType.Module) {
|
||||
return;
|
||||
}
|
||||
|
||||
const moduleSymbol = globalScope.lookUpSymbol(nameNode.d.value);
|
||||
if (!moduleSymbol || !this._isViperGlobalRuntimeVariable(moduleSymbol)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const symbolWithScope = this._evaluator.lookUpSymbolRecursive(
|
||||
nameNode,
|
||||
nameNode.d.value,
|
||||
/* honorCodeFlow */ false
|
||||
);
|
||||
const hasGlobalBinding =
|
||||
!!symbolWithScope?.usesGlobalBinding || !!symbolWithScope?.usesNonlocalBinding;
|
||||
|
||||
this._evaluator.addDiagnostic(
|
||||
DiagnosticRule.reportGeneralTypeIssues,
|
||||
hasGlobalBinding
|
||||
? LocMessage.viperGlobalRedeclarationWithGlobal().format({ name: nameNode.d.value })
|
||||
: LocMessage.viperGlobalRedeclarationWithoutGlobal().format({ name: nameNode.d.value }),
|
||||
nameNode
|
||||
);
|
||||
}
|
||||
|
||||
// Viper 扩展:模块级符号是否为「运行期变量」(会占据全局存储、可被同名注解遮蔽)。
|
||||
// 排除函数/类/导入别名/显式类型别名声明,以及不产生全局存储的编译期元指令:
|
||||
// - CDefine 宏常量:编译期展开,无全局变量(与 TPV 的模块变量表同口径);
|
||||
// - CTypedef 别名:仅登记类型,不创建全局符号。
|
||||
private _isViperGlobalRuntimeVariable(moduleSymbol: Symbol): boolean {
|
||||
const declarations = moduleSymbol.getDeclarations();
|
||||
const isVariable = declarations.some(
|
||||
(decl) =>
|
||||
decl.type === DeclarationType.Variable &&
|
||||
!(
|
||||
decl.typeAnnotationNode?.nodeType === ParseNodeType.MemberAccess &&
|
||||
decl.typeAnnotationNode.d.member.d.value === 'CTypedef'
|
||||
)
|
||||
);
|
||||
|
||||
if (!isVariable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isCDefineType(this._evaluator.getEffectiveTypeOfSymbol(moduleSymbol));
|
||||
}
|
||||
|
||||
// Verifies that the target of a nonlocal statement is not a PEP 695-style
|
||||
// TypeParameter. This situation results in a runtime exception.
|
||||
private _validateNonlocalTypeParam(node: NameNode) {
|
||||
|
||||
@@ -63,8 +63,10 @@ import {
|
||||
isClassInstance,
|
||||
isCConstType,
|
||||
isCDefineType,
|
||||
isCModifierType,
|
||||
isCPtrType,
|
||||
isCStaticType,
|
||||
isCVolatileType,
|
||||
isCTypeSubclassType,
|
||||
isCVoidType,
|
||||
isFunctionOrOverloaded,
|
||||
@@ -123,6 +125,7 @@ export function validateBinaryOperation(
|
||||
leftTypeResult: TypeResult,
|
||||
rightTypeResult: TypeResult,
|
||||
errorNode: ExpressionNode,
|
||||
leftExpression: ExpressionNode,
|
||||
inferenceContext: InferenceContext | undefined,
|
||||
diag: DiagnosticAddendum,
|
||||
options: BinaryOperationOptions
|
||||
@@ -251,14 +254,11 @@ export function validateBinaryOperation(
|
||||
// `(T*)(x)`,结果类型为指针类型 CPtr[T] 的实例。
|
||||
// 左操作数为真值类型(int/float 等)时不受影响:`2 * 3`、`offset * 2`
|
||||
// 等真实乘法仍走正常路径。
|
||||
if (
|
||||
!type &&
|
||||
IsViperDialect(getFileInfo(errorNode).executionEnvironment.dialect) &&
|
||||
operator === OperatorType.Multiply &&
|
||||
isInstantiableClass(leftType)
|
||||
) {
|
||||
const typeArg = isClass(leftType) && !isClassInstance(leftType) ? convertToInstance(leftType) : leftType;
|
||||
type = getViperCPtrTypeForTypeArg(evaluator, errorNode, typeArg);
|
||||
if (!type && IsViperDialect(getFileInfo(errorNode).executionEnvironment.dialect) && operator === OperatorType.Multiply) {
|
||||
const typeArg = getViperCastTypeArg(evaluator, leftExpression, leftType);
|
||||
if (typeArg) {
|
||||
type = getViperCPtrTypeForTypeArg(evaluator, errorNode, typeArg);
|
||||
}
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
@@ -404,7 +404,10 @@ export function getTypeOfBinaryOperation(
|
||||
adjustedLeftType = convertToInstantiable(evaluator.getNoneType());
|
||||
}
|
||||
|
||||
if (isUnionableType([adjustedLeftType, adjustedRightType])) {
|
||||
if (
|
||||
isUnionableType([adjustedLeftType, adjustedRightType]) ||
|
||||
isViperTypeDescriptorUnion(node, adjustedLeftType, adjustedRightType)
|
||||
) {
|
||||
if (isInstantiableClass(adjustedLeftType)) {
|
||||
adjustedLeftType = specializeWithDefaultTypeArgs(adjustedLeftType);
|
||||
}
|
||||
@@ -470,6 +473,7 @@ export function getTypeOfBinaryOperation(
|
||||
{ type: leftType, isIncomplete: leftTypeResult.isIncomplete },
|
||||
{ type: rightType, isIncomplete: rightTypeResult.isIncomplete },
|
||||
node,
|
||||
node.d.leftExpr,
|
||||
inferenceContext,
|
||||
diag,
|
||||
{ isLiteralMathAllowed, isTupleAddAllowed }
|
||||
@@ -673,6 +677,7 @@ export function getTypeOfAugmentedAssignment(
|
||||
{ type: leftSubtypeUnexpanded, isIncomplete: leftTypeResult.isIncomplete },
|
||||
{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete },
|
||||
node,
|
||||
node.d.leftExpr,
|
||||
inferenceContext,
|
||||
diag,
|
||||
{ isLiteralMathAllowed, isTupleAddAllowed }
|
||||
@@ -756,6 +761,15 @@ export function getTypeOfUnaryOperation(
|
||||
// 如 t.CInt、ast.AST),则 `T*` 是 C 指针类型表达式 CPtr[T],而非对值的解引用。
|
||||
// 于是 `(T*)x`、`(T*)(x)`、`T*(x)` 中的括号内/实参里的 `T*` 都被求值为指针类型。
|
||||
// 只有真值(CPtr / str / bytes / 数值等)才走解引用路径。
|
||||
//
|
||||
// Viper 扩展:parser 标记的「指针类型后缀」节点(`_parseViperPointerTypeSuffix`
|
||||
// 打上 isViperPointerTypeSuffix)在值上下文里一律是类型表达式,不做解引用。
|
||||
// 多级指针 `T**`(AST = *(*T))的外层 '*' 其操作数是内层已求值出的 CPtr[T]
|
||||
// 实例(非可实例化类),若按上一条判据会落入解引用 → 得到 T,使
|
||||
// `(T**)(x)` 的括号内类型退化成 T(报"T 的对象不可调用")并丢失二级指针。
|
||||
if (node.d.isViperPointerTypeSuffix) {
|
||||
return getTypeOfViperPointerAnnotation(evaluator, node);
|
||||
}
|
||||
if (isInstantiableClass(exprType)) {
|
||||
return getTypeOfViperPointerAnnotation(evaluator, node);
|
||||
}
|
||||
@@ -984,6 +998,7 @@ function getTypeOfViperPointerAnnotation(evaluator: TypeEvaluator, node: UnaryOp
|
||||
const innerType = innerResult.type;
|
||||
const resultTypes: Type[] = [];
|
||||
let sawInvalidType = false;
|
||||
const isViperDialect = IsViperDialect(getFileInfo(node).executionEnvironment.dialect);
|
||||
|
||||
mapSubtypes(innerType, (subtype) => {
|
||||
if (isAnyOrUnknown(subtype)) {
|
||||
@@ -991,7 +1006,21 @@ function getTypeOfViperPointerAnnotation(evaluator: TypeEvaluator, node: UnaryOp
|
||||
return subtype;
|
||||
}
|
||||
|
||||
const typeArg = isClass(subtype) && !isClassInstance(subtype) ? convertToInstance(subtype) : subtype;
|
||||
// Viper 扩展:类型描述符(const / volatile / static 等修饰符)不出现在指针
|
||||
// 的类型参数里,原样保留在结果中:
|
||||
// `const int*`(AST = `(CConst | int)*`)→ `CConst | CPtr[int]`
|
||||
// 与 Viper 惯用的 `CConst | CChar | CPtr`(const char*)表示一致,
|
||||
// 避免产生 `CPtr[CConst] | CPtr[int]` 这类"指针的联合"。
|
||||
if (isViperDialect && isClass(subtype) && isCModifierType(subtype)) {
|
||||
resultTypes.push(subtype);
|
||||
return subtype;
|
||||
}
|
||||
|
||||
// Viper 扩展:`T*` 的 T 是裸模块名、且模块内有同名类时(见
|
||||
// getViperModuleSameNameClass),取该类作为类型参数:
|
||||
// `bootinfo*(saved_rdi)` → CPtr[bootinfo](而非 CPtr[Module])。
|
||||
const sameNameClass = getViperModuleSameNameClass(evaluator, node.d.expr, subtype);
|
||||
const typeArg = sameNameClass ?? (isClass(subtype) && !isClassInstance(subtype) ? convertToInstance(subtype) : subtype);
|
||||
const cptrType = getViperCPtrTypeForTypeArg(evaluator, node, typeArg);
|
||||
if (cptrType) {
|
||||
resultTypes.push(cptrType);
|
||||
@@ -1037,32 +1066,91 @@ function getTypeOfViperAddrOf(
|
||||
|
||||
// 构造 CPtr[typeArg](Viper 指针类型),通过 t 模块的 CPtr 泛型类特化。
|
||||
function getViperCPtrTypeForTypeArg(evaluator: TypeEvaluator, node: ParseNode, typeArg: Type): Type | undefined {
|
||||
const cptrClass = getViperCPtrClass(evaluator, node);
|
||||
if (cptrClass) {
|
||||
return convertToInstance(ClassType.specialize(cptrClass, [typeArg]));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 解析 Viper C 风格强转 `T*(x)` 左操作数 `T` 的被指向类型。
|
||||
// - 常规:`T` 是可实例化类(t.CInt / bootinfo.bootinfo 等)→ 取其**实例**形态作为被指向类型;
|
||||
// - 模块同名类:见 getViperModuleSameNameClass;
|
||||
// - 其余(真值类型)返回 undefined,不兜底:交由常规算术校验给出真实报错。
|
||||
function getViperCastTypeArg(evaluator: TypeEvaluator, leftExpression: ExpressionNode, leftType: Type): Type | undefined {
|
||||
if (isInstantiableClass(leftType)) {
|
||||
return isClass(leftType) && !isClassInstance(leftType) ? convertToInstance(leftType) : leftType;
|
||||
}
|
||||
|
||||
return getViperModuleSameNameClass(evaluator, leftExpression, leftType);
|
||||
}
|
||||
|
||||
// Viper 扩展:裸名同时是「模块名」与「模块内同名类」时,取该类(实例形态)。
|
||||
// 典型场景(ViperOS VKernel/Kernel/main.vp):`import bootinfo`(模块)与
|
||||
// bootinfo.vp 的 `class bootinfo` 同名,代码写作 `BootInfo = bootinfo*(saved_rdi)`。
|
||||
// TPC 的 struct 注册表按短名全局查找,故裸名 bootinfo 取到该结构体类;LSP 按
|
||||
// 「模块内同名类」等价还原。模块内没有同名类时返回 undefined(不兜底)。
|
||||
function getViperModuleSameNameClass(
|
||||
evaluator: TypeEvaluator,
|
||||
exprNode: ExpressionNode,
|
||||
type: Type
|
||||
): Type | undefined {
|
||||
if (!isModule(type) || exprNode.nodeType !== ParseNodeType.Name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sameNameSymbol = ModuleType.getField(type, exprNode.d.value);
|
||||
if (!sameNameSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const memberType = evaluator.getEffectiveTypeOfSymbolForUsage(
|
||||
sameNameSymbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
return isInstantiableClass(memberType) ? convertToInstance(memberType) : undefined;
|
||||
}
|
||||
|
||||
// 取 Viper 的 CPtr 泛型类(可实例化形式)。
|
||||
// - 首选源码 `import t` 可见的 `t.CPtr`;
|
||||
// - 回退到 builtins stub 的编译器内置 `rawptr`(等价 t.CPtr,见 builtins.vpi),
|
||||
// 使 `T*` / `&x` 指针语法在未 `import t` 的文件中同样可用
|
||||
// (与 T* 注解、typedef、[SIZE]T 一致:不强制源码 import t)。
|
||||
function getViperCPtrClass(evaluator: TypeEvaluator, node: ParseNode): ClassType | undefined {
|
||||
const tSymbolWithScope = evaluator.lookUpSymbolRecursive(node, 't', /* honorCodeFlow */ true);
|
||||
if (!tSymbolWithScope) {
|
||||
return undefined;
|
||||
if (tSymbolWithScope) {
|
||||
const tEffectiveType = evaluator.getEffectiveTypeOfSymbolForUsage(
|
||||
tSymbolWithScope.symbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (isModule(tEffectiveType)) {
|
||||
const cptrSymbol = ModuleType.getField(tEffectiveType, 'CPtr');
|
||||
if (cptrSymbol) {
|
||||
const cptrType = evaluator.getEffectiveTypeOfSymbolForUsage(
|
||||
cptrSymbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (isInstantiableClass(cptrType)) {
|
||||
return cptrType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tEffectiveType = evaluator.getEffectiveTypeOfSymbolForUsage(
|
||||
tSymbolWithScope.symbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (!isModule(tEffectiveType)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cptrSymbol = ModuleType.getField(tEffectiveType, 'CPtr');
|
||||
if (!cptrSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cptrType = evaluator.getEffectiveTypeOfSymbolForUsage(
|
||||
cptrSymbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (isInstantiableClass(cptrType)) {
|
||||
return convertToInstance(ClassType.specialize(cptrType, [typeArg]));
|
||||
// 回退:builtins 中的 rawptr(= t.CPtr)。
|
||||
const rawptrSymbolWithScope = evaluator.lookUpSymbolRecursive(node, 'rawptr', /* honorCodeFlow */ true);
|
||||
if (rawptrSymbolWithScope) {
|
||||
const rawptrType = evaluator.getEffectiveTypeOfSymbolForUsage(
|
||||
rawptrSymbolWithScope.symbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (isInstantiableClass(rawptrType)) {
|
||||
return rawptrType;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -1118,6 +1206,57 @@ export function getTypeOfTernaryOperation(
|
||||
return { type: combineTypes(typesToCombine), isIncomplete, typeErrors };
|
||||
}
|
||||
|
||||
// Viper 扩展:`X | t.CPtr` 的 Viper 语义是"指向 X 的指针"(X*)。combineTypes 里的
|
||||
// Struct | CPtr 折叠覆盖了 X 不是指针的情形;但当 X 自身就是指针时(如
|
||||
// `HANDLE = VOIDPTR = t.CPtr`,两侧在类型层完全同形,都是 type[CPtr[Unknown]]),
|
||||
// 类型层已无法区分"外层指针模板"与"被指向类型",两者会被并成一级指针,使
|
||||
// `HANDLE | t.CPtr` 从 void**(void 的指针的指针)退化成 void*,于是取值
|
||||
// `&handle`(void**)传参/返回会被报"跨级指针隐式转换不允许"(误报)。
|
||||
// 此处按语法分辨:右操作数就是裸 CPtr 类(`t.CPtr` / `rawptr`)且左操作数已是指针
|
||||
// 形态时,直接把左操作数包一层指针(保持输入的实例 / 可实例化形态)。
|
||||
function maybeWrapViperCPtrUnion(
|
||||
node: BinaryOperationNode,
|
||||
flags: EvalFlags,
|
||||
leftType: Type,
|
||||
rightType: Type
|
||||
): Type | undefined {
|
||||
// 只在类型表达式位置生效,值上下文的 `a | b` 不受影响。
|
||||
if ((flags & EvalFlags.TypeExpression) === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isViperBareCPtrClassReference(node.d.rightExpr)) {
|
||||
return undefined;
|
||||
}
|
||||
// 右操作数须确实解析为 CPtr 类(挡住用户自定义的同名成员,如 `Foo.CPtr`)。
|
||||
if (!isClass(rightType) || !isCPtrType(rightType)) {
|
||||
return undefined;
|
||||
}
|
||||
// 左操作数须已是指针;否则交给 combineTypes 的 Struct | CPtr 折叠
|
||||
// (如 `Scheduler | t.CPtr` → CPtr[Scheduler]、`CConst | CChar | CPtr` → const char*)。
|
||||
if (!someSubtypes(leftType, (subtype) => isClass(subtype) && isCPtrType(subtype))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isInstantiableForm = TypeBase.isInstantiable(leftType);
|
||||
const pointee = isClass(leftType) && isInstantiableForm ? ClassType.cloneAsInstance(leftType) : leftType;
|
||||
const template = TypeBase.isInstantiable(rightType) ? rightType : ClassType.cloneAsInstantiable(rightType);
|
||||
const wrapped = ClassType.specialize(template, [pointee]);
|
||||
return isInstantiableForm ? wrapped : ClassType.cloneAsInstance(wrapped);
|
||||
}
|
||||
|
||||
/** Viper:右操作数是否是裸 CPtr 类引用(`t.CPtr` / `CPtr` / `rawptr`,不带下标实参)。 */
|
||||
function isViperBareCPtrClassReference(node: ExpressionNode): boolean {
|
||||
const isCPtrName = (name: string) => name === 'CPtr' || name === 'rawptr';
|
||||
|
||||
if (node.nodeType === ParseNodeType.Name) {
|
||||
return isCPtrName(node.d.value);
|
||||
}
|
||||
if (node.nodeType === ParseNodeType.MemberAccess) {
|
||||
return node.d.member.nodeType === ParseNodeType.Name && isCPtrName(node.d.member.d.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function createUnionType(
|
||||
evaluator: TypeEvaluator,
|
||||
node: BinaryOperationNode,
|
||||
@@ -1166,6 +1305,13 @@ function createUnionType(
|
||||
flags | EvalFlags.InstantiableType
|
||||
);
|
||||
|
||||
// Viper 扩展:`X | t.CPtr` 且 X 已是指针时,按"包一层指针"处理
|
||||
// (见 maybeWrapViperCPtrUnion),此时结果不是联合类型,直接返回。
|
||||
const wrappedPointerType = maybeWrapViperCPtrUnion(node, flags, adjustedLeftType, adjustedRightType);
|
||||
if (wrappedPointerType) {
|
||||
return { type: wrappedPointerType };
|
||||
}
|
||||
|
||||
let newUnion = combineTypes([adjustedLeftType, adjustedRightType], { skipElideRedundantLiterals: true });
|
||||
|
||||
// Viper 扩展:若 combineTypes 已将 Struct | CPtr 折叠为 CPtr[Struct],
|
||||
@@ -1392,6 +1538,29 @@ function calcLiteralForBinaryOp(operator: OperatorType, leftType: Type, rightTyp
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Viper 扩展:判断 `|` 是否为「类型描述符组合」(TypeSet),如
|
||||
// `CConst | int`(const int)、`CConst | int*`(const int*,右侧求值为 CPtr[int] 实例)。
|
||||
// Python 的 `|` 走联合要求两侧同为可实例化类型;而 `T*` 求值出的是**实例**
|
||||
// (CPtr[int]),故 `CConst | int*` 会落到 `__or__` 分支并退化为 Unknown。
|
||||
// 描述符(const / volatile / static 等)本身只是编译期修饰,不含运行时 `__or__`,
|
||||
// 这里把「任一侧含描述符」的组合按联合处理,交由 combineTypes 归一。
|
||||
// 注:两侧同为可实例化类型的组合(如 CConst | CChar | CPtr)本就走 isUnionableType,
|
||||
// 不受此处影响。
|
||||
function isViperTypeDescriptorUnion(node: ParseNode, leftType: Type, rightType: Type): boolean {
|
||||
if (!IsViperDialect(getFileInfo(node).executionEnvironment.dialect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const containsDescriptor = (type: Type): boolean => {
|
||||
if (isUnion(type)) {
|
||||
return type.priv.subtypes.some((subtype) => isCModifierType(subtype));
|
||||
}
|
||||
return isCModifierType(type);
|
||||
};
|
||||
|
||||
return containsDescriptor(leftType) || containsDescriptor(rightType);
|
||||
}
|
||||
|
||||
function customMetaclassSupportsMethod(type: Type, methodName: string): boolean {
|
||||
if (!isInstantiableClass(type)) {
|
||||
return false;
|
||||
@@ -1594,7 +1763,8 @@ function validateArithmeticOperation(
|
||||
if (isViperDialect) {
|
||||
if (
|
||||
isCStaticType(leftSubtypeUnexpanded) || isCStaticType(rightSubtypeUnexpanded) ||
|
||||
isCConstType(leftSubtypeUnexpanded) || isCConstType(rightSubtypeUnexpanded)
|
||||
isCConstType(leftSubtypeUnexpanded) || isCConstType(rightSubtypeUnexpanded) ||
|
||||
isCVolatileType(leftSubtypeUnexpanded) || isCVolatileType(rightSubtypeUnexpanded)
|
||||
) {
|
||||
return UnknownType.create(isIncomplete);
|
||||
}
|
||||
|
||||
@@ -259,8 +259,10 @@ import {
|
||||
getCPtrLevel,
|
||||
isCConstType,
|
||||
isCStaticType,
|
||||
isCVolatileType,
|
||||
isCModifierType,
|
||||
isPureCModifierType,
|
||||
isViperAttrMarkerType,
|
||||
isCEnumType,
|
||||
isCEnumSubclassType,
|
||||
isCTypeSubclassType,
|
||||
@@ -3302,7 +3304,8 @@ export function createTypeEvaluator(
|
||||
const typeContainsCPtr = findSubtype(type, (s) =>
|
||||
isClassInstance(s) && isCPtrType(s)) !== undefined;
|
||||
if (typeContainsCPtr && (
|
||||
isCConstType(subtype) || isCStaticType(subtype) || isCVoidType(subtype) ||
|
||||
isCConstType(subtype) || isCStaticType(subtype) || isCVolatileType(subtype) ||
|
||||
isCVoidType(subtype) ||
|
||||
ClassType.isBuiltIn(subtype, 'int')
|
||||
)) {
|
||||
return UnknownType.create(isIncomplete);
|
||||
@@ -3453,7 +3456,8 @@ export function createTypeEvaluator(
|
||||
const typeContainsCPtr = findSubtype(type, (s) =>
|
||||
isClassInstance(s) && isCPtrType(s)) !== undefined;
|
||||
if (typeContainsCPtr && (
|
||||
isCConstType(subtype) || isCStaticType(subtype) || isCVoidType(subtype) ||
|
||||
isCConstType(subtype) || isCStaticType(subtype) || isCVolatileType(subtype) ||
|
||||
isCVoidType(subtype) ||
|
||||
ClassType.isBuiltIn(subtype, 'int')
|
||||
)) {
|
||||
return UnknownType.create();
|
||||
@@ -3891,6 +3895,8 @@ export function createTypeEvaluator(
|
||||
// Viper 扩展:禁止函数内部隐式写入全局变量(无 global 声明且无局部声明)。
|
||||
// 规则:
|
||||
// 1. 全局变量存在 + 局部再次声明赋值(无 global)→ 不报错(局部优先)
|
||||
// (注:**带类型注解**的局部再声明不适用本条——那属「重复声明」,
|
||||
// 由 checker._validateViperGlobalRedeclaration 按 TPV 口径报 TypeError)
|
||||
// 2. 全局变量存在 + 局部无声明 + 有 global + 赋值 → 不报错
|
||||
// 3. 全局变量存在 + 局部无声明 + 无 global + 仅读取 → 不报错(读取处不检查)
|
||||
// 4. 全局变量存在 + 局部无声明 + 无 global + 赋值 → 报错
|
||||
@@ -5367,6 +5373,12 @@ export function createTypeEvaluator(
|
||||
// import t,合成 `t` 无法解析时同样静默退化为 Unknown;
|
||||
// TypeAlias 识别只看注解成员名文本 'CTypedef',不依赖 t 符号。
|
||||
type = UnknownType.create();
|
||||
} else if (node.d.isViperTypeDescriptor) {
|
||||
// Viper 扩展:类型描述符 `const T` / `volatile T` 脱糖出的合成
|
||||
// `CConst` / `CVolatile` 名(见 parser._parseViperTypeDescriptorPrefix)。
|
||||
// 它们是 builtins stub 中的别名,正常 Viper 工程均可解析;
|
||||
// 解析不到时静默退化为 Unknown,不报「未定义」(与 [SIZE]T 同一取舍)。
|
||||
type = UnknownType.create();
|
||||
} else {
|
||||
// Viper 扩展:前向引用支持(收紧版)。
|
||||
// Viper 是 AOT 语言,函数/类的定义顺序无关(类似 C 语言的前向声明),
|
||||
@@ -6073,6 +6085,19 @@ export function createTypeEvaluator(
|
||||
}
|
||||
|
||||
function getTypeOfMemberAccess(node: MemberAccessNode, flags: EvalFlags): TypeResult {
|
||||
// Viper 扩展:注解位置的 `t.attr.<name>`(如 `t.attr.llvm.nounwind`)属性标记
|
||||
// 求值为 t.CAttr,避免其泄漏为联合成员(`CInt | nounwind`)。仅类型表达式
|
||||
// 上下文拦截,保证成员补全(`t.attr.llvm.`)等值上下文路径不受影响。
|
||||
if (
|
||||
(flags & EvalFlags.TypeExpression) !== 0 &&
|
||||
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect)
|
||||
) {
|
||||
const markerType = getViperAttributeMarkerType(node);
|
||||
if (markerType) {
|
||||
return { type: markerType };
|
||||
}
|
||||
}
|
||||
|
||||
// Compute flags specifically for evaluating the left expression.
|
||||
let leftExprFlags = EvalFlags.MemberAccessBaseDefaults;
|
||||
leftExprFlags |=
|
||||
@@ -8174,7 +8199,7 @@ export function createTypeEvaluator(
|
||||
|
||||
// Viper 扩展:TypeSet 类型组合的下标访问语义。
|
||||
// Viper 中 `|` 表示类型组合(如 CConst | str = const char*),
|
||||
// 对 TypeSet 中的修饰类型(CConst/CStatic/CPtr/CVoid/CDefine/CArray)跳过下标访问,
|
||||
// 对 TypeSet 中的修饰类型(CConst/CStatic/CPtr/CVoid/CDefine/CArray/CAttr)跳过下标访问,
|
||||
// 让 combineTypes 只合并非修饰类型的结果(如 str/bytes 的下标返回 int/CChar)。
|
||||
const viperIndexFileInfo = AnalyzerNodeInfo.getFileInfo(node);
|
||||
const isViperTypeSetIndex =
|
||||
@@ -8185,9 +8210,11 @@ export function createTypeEvaluator(
|
||||
isClassInstance(subtype) &&
|
||||
(isCConstType(subtype) ||
|
||||
isCStaticType(subtype) ||
|
||||
isCVolatileType(subtype) ||
|
||||
isCPtrType(subtype) ||
|
||||
isCVoidType(subtype) ||
|
||||
isCDefineType(subtype) ||
|
||||
isViperAttrMarkerType(subtype) ||
|
||||
isCArrayType(subtype))
|
||||
);
|
||||
|
||||
@@ -8215,9 +8242,11 @@ export function createTypeEvaluator(
|
||||
isClassInstance(concreteSubtype) &&
|
||||
(isCConstType(concreteSubtype) ||
|
||||
isCStaticType(concreteSubtype) ||
|
||||
isCVolatileType(concreteSubtype) ||
|
||||
isCPtrType(concreteSubtype) ||
|
||||
isCVoidType(concreteSubtype) ||
|
||||
isCDefineType(concreteSubtype) ||
|
||||
isViperAttrMarkerType(concreteSubtype) ||
|
||||
isCArrayType(concreteSubtype))
|
||||
) {
|
||||
return undefined;
|
||||
@@ -8808,7 +8837,8 @@ export function createTypeEvaluator(
|
||||
// 不报错,让 mapSubtypesExpandTypeVars 处理其他子类型时获得正确方法。
|
||||
if (
|
||||
IsViperDialect(fileInfo.executionEnvironment.dialect) &&
|
||||
(isCConstType(baseType) || isCStaticType(baseType) || isCPtrType(baseType) || isCVoidType(baseType) ||
|
||||
(isCConstType(baseType) || isCStaticType(baseType) || isCVolatileType(baseType) ||
|
||||
isCPtrType(baseType) || isCVoidType(baseType) ||
|
||||
isCDefineType(baseType) || isCArrayType(baseType))
|
||||
) {
|
||||
return { type: UnknownType.create() };
|
||||
@@ -9412,6 +9442,61 @@ export function createTypeEvaluator(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Viper 扩展:解析 `t.attr.<name>` / `t.attr.<name>(...)` 属性标记 → t.CAttr 实例。
|
||||
//
|
||||
// 形态(与 TransPyC HandlesAssign._TAttrName / TransPyV ClassGenBase._tattr_name
|
||||
// 同判据:只看最外层属性名):
|
||||
// t.attr.packed (Attribute:t.attr.llvm.nounwind → 最外层 nounwind)
|
||||
// t.attr.section(".x") (Call:取 func 的最外层属性名)
|
||||
// 这类表达式出现在类型注解联合中(`T | t.attr.section(".x")`、
|
||||
// `T | t.attr.llvm.nounwind`),或由 parser 从 `__attribute__(...)` 修饰器脱糖而来;
|
||||
// 注解位置求值为 CAttr 标记——与 CConst / CVolatile 同族的修饰符
|
||||
// (isViperAttrMarkerType → isCModifierType),只携带编译期元数据,
|
||||
// 不参与类型检查(运算/下标/赋值按被修饰的基础类型处理),展示时也不泄漏为联合成员。
|
||||
// 返回 undefined 表示该表达式不是 t.attr 属性标记。
|
||||
function getViperAttributeMarkerType(node: ExpressionNode): Type | undefined {
|
||||
let cur: ExpressionNode = node;
|
||||
if (cur.nodeType === ParseNodeType.Call) {
|
||||
cur = cur.d.leftExpr;
|
||||
}
|
||||
const members: string[] = [];
|
||||
while (cur.nodeType === ParseNodeType.MemberAccess) {
|
||||
members.unshift(cur.d.member.d.value);
|
||||
cur = cur.d.leftExpr;
|
||||
}
|
||||
// 形态:<root>.attr.<name>[...];至少两级成员且首段为 attr。
|
||||
if (cur.nodeType !== ParseNodeType.Name || members.length < 2 || members[0] !== 'attr') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const moduleSymbolWithScope = lookUpSymbolRecursive(cur, cur.d.value, /* honorCodeFlow */ true);
|
||||
if (!moduleSymbolWithScope) {
|
||||
return undefined;
|
||||
}
|
||||
const moduleType = getEffectiveTypeOfSymbolForUsage(
|
||||
moduleSymbolWithScope.symbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (!moduleType || !isModule(moduleType)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const attrMarkerClassSymbol = ModuleType.getField(moduleType, 'CAttr');
|
||||
if (!attrMarkerClassSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
const attrMarkerClassType = getEffectiveTypeOfSymbolForUsage(
|
||||
attrMarkerClassSymbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (attrMarkerClassType && isInstantiableClass(attrMarkerClassType)) {
|
||||
return convertToInstance(attrMarkerClassType);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Viper 扩展:判断调用节点是否为内置 enumerate 调用。
|
||||
// Viper 语义下 enumerate 的迭代元素类型按 for 原迭代逻辑计算
|
||||
//(str/bytes = i8* → t.CChar、CPtr[T] → T),索引类型为 t.CSizeT(size_t)。
|
||||
@@ -9709,6 +9794,19 @@ export function createTypeEvaluator(
|
||||
flags: EvalFlags,
|
||||
inferenceContext: InferenceContext | undefined
|
||||
): TypeResult {
|
||||
// Viper 扩展:注解位置的 `t.attr.<name>(...)`(如 `t.attr.section(".x")`、
|
||||
// 由 `__attribute__(...)` 修饰器脱糖而来)属性标记求值为 t.CAttr;
|
||||
// 顺带跳过「类型注解中不允许调用」的 typeAnnotationCall 诊断。
|
||||
if (
|
||||
(flags & EvalFlags.TypeExpression) !== 0 &&
|
||||
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect)
|
||||
) {
|
||||
const markerType = getViperAttributeMarkerType(node);
|
||||
if (markerType) {
|
||||
return { type: markerType };
|
||||
}
|
||||
}
|
||||
|
||||
let baseTypeResult: TypeResult | undefined;
|
||||
|
||||
// Check for the use of `type(x)` within a type annotation. This isn't
|
||||
@@ -26687,7 +26785,7 @@ export function createTypeEvaluator(
|
||||
if (srcIsStrBytes) {
|
||||
const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType];
|
||||
const hasCStringLike = destVariants.some((variant) =>
|
||||
isCConstType(variant) || isCPtrType(variant) || isCVoidType(variant)
|
||||
isCConstType(variant) || isCVolatileType(variant) || isCPtrType(variant) || isCVoidType(variant)
|
||||
);
|
||||
if (hasCStringLike) {
|
||||
return true;
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
isTypeVarTuple,
|
||||
isUnknown,
|
||||
isUnpacked,
|
||||
isViperAttrMarkerType,
|
||||
maxTypeRecursionCount,
|
||||
OverloadedType,
|
||||
SentinelLiteral,
|
||||
@@ -572,6 +573,21 @@ function printTypeInternal(
|
||||
return specialFormText;
|
||||
}
|
||||
|
||||
// Viper 扩展:联合类型中的类型描述符按**前缀限定符**渲染,而不是
|
||||
// 联合成员:`CConst | CPtr[int]` → `const rawptr[int]`、
|
||||
// `CVolatile | int` → `volatile int`。见 printViperModifierPrefix。
|
||||
const modifierPrefixText = printViperModifierPrefix(
|
||||
type,
|
||||
printTypeFlags,
|
||||
returnTypeCallback,
|
||||
uniqueNameMap,
|
||||
recursionTypes,
|
||||
recursionCount
|
||||
);
|
||||
if (modifierPrefixText !== undefined) {
|
||||
return modifierPrefixText;
|
||||
}
|
||||
|
||||
// If we're using "|" notation, enclose callable subtypes in parens.
|
||||
const updatedPrintTypeFlags =
|
||||
printTypeFlags & PrintTypeFlags.PEP604
|
||||
@@ -706,6 +722,113 @@ function printTypeInternal(
|
||||
}
|
||||
}
|
||||
|
||||
// Viper 扩展:联合类型中修饰符(类型描述符)对应的展示关键字。
|
||||
// Viper 用联合(TypeSet)表示「类型 + 描述符」,如 `CConst | CPtr[int]`(const int*)、
|
||||
// `CVolatile | int`(volatile int)。展示时须把它们当作**前缀限定符**,
|
||||
// 而不是联合成员,否则会暴露出内部表示("CConst | rawptr[int]")。
|
||||
// 仅覆盖有唯一 C 关键字的修饰符;State / CExport / CPass / CDefine
|
||||
// (声明标记、编译期元指令)另有语义,保持原样。
|
||||
function getViperModifierKeyword(type: Type): string | undefined {
|
||||
if (!isClass(type)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (type.shared.name) {
|
||||
case 'CConst':
|
||||
return 'const';
|
||||
case 'CVolatile':
|
||||
return 'volatile';
|
||||
case 'CStatic':
|
||||
return 'static';
|
||||
case 'CRegister':
|
||||
return 'register';
|
||||
case 'CAuto':
|
||||
return 'auto';
|
||||
case 'CInline':
|
||||
return 'inline';
|
||||
case 'CExtern':
|
||||
return 'extern';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Viper 扩展:把联合类型渲染为「修饰符前缀 + 其余类型」。
|
||||
// `CConst | CPtr[int]` → `const rawptr[int]`
|
||||
// `CVolatile | int` → `volatile int`
|
||||
// `CConst | CVolatile | int` → `const volatile int`(修饰符全部提到最前)
|
||||
// `CConst | int | str` → `const (int | str)`(其余多于一个时加括号,
|
||||
// 避免前缀限定范围歧义)
|
||||
// 无修饰符(或全是修饰符)时返回 undefined,交回常规联合渲染。
|
||||
function printViperModifierPrefix(
|
||||
type: UnionType,
|
||||
printTypeFlags: PrintTypeFlags,
|
||||
returnTypeCallback: FunctionReturnTypeCallback,
|
||||
uniqueNameMap: UniqueNameMap,
|
||||
recursionTypes: Type[],
|
||||
recursionCount: number
|
||||
): string | undefined {
|
||||
const modifierKeywords: string[] = [];
|
||||
const remainingSubtypes: Type[] = [];
|
||||
let sawAttrMarker = false;
|
||||
|
||||
for (const subtype of type.priv.subtypes) {
|
||||
// Viper 扩展:`__attribute__` 属性标记(t.CAttr)在源码中没有对应关键字,
|
||||
// 展示时直接丢弃:`CInt | t.attr.weak` → `int`。
|
||||
if (isViperAttrMarkerType(subtype)) {
|
||||
sawAttrMarker = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const keyword = getViperModifierKeyword(subtype);
|
||||
if (keyword !== undefined) {
|
||||
modifierKeywords.push(keyword);
|
||||
} else {
|
||||
remainingSubtypes.push(subtype);
|
||||
}
|
||||
}
|
||||
|
||||
if ((modifierKeywords.length === 0 && !sawAttrMarker) || remainingSubtypes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (recursionCount >= maxTypeRecursionCount) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
recursionTypes.push(type);
|
||||
try {
|
||||
let remainingText: string;
|
||||
if (remainingSubtypes.length === 1) {
|
||||
remainingText = printTypeInternal(
|
||||
remainingSubtypes[0],
|
||||
printTypeFlags,
|
||||
returnTypeCallback,
|
||||
uniqueNameMap,
|
||||
recursionTypes,
|
||||
recursionCount + 1
|
||||
);
|
||||
} else {
|
||||
const parts = remainingSubtypes.map((subtype) =>
|
||||
printTypeInternal(
|
||||
subtype,
|
||||
printTypeFlags,
|
||||
returnTypeCallback,
|
||||
uniqueNameMap,
|
||||
recursionTypes,
|
||||
recursionCount + 1
|
||||
)
|
||||
);
|
||||
remainingText = `(${parts.join(' | ')})`;
|
||||
}
|
||||
|
||||
const modifierText = modifierKeywords.join(' ');
|
||||
return modifierText ? `${modifierText} ${remainingText}` : remainingText;
|
||||
} finally {
|
||||
recursionTypes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
function printUnionType(
|
||||
type: UnionType,
|
||||
printTypeFlags: PrintTypeFlags,
|
||||
|
||||
@@ -3299,13 +3299,35 @@ export function isCStaticType(type: Type): boolean {
|
||||
return type.shared.name === 'CStatic' || type.shared.fullName === 't.CStatic';
|
||||
}
|
||||
|
||||
// Viper 扩展:检测类型是否为 t.CVolatile(volatile 类型限定符)。
|
||||
// CVolatile 在联合类型中用于表示 volatile 修饰(如 CVolatile | CInt 表示 volatile int)。
|
||||
// 与 isCConstType 对称:语义上不影响数据类型,仅在 TypeSet 联合中充当描述符。
|
||||
export function isCVolatileType(type: Type): boolean {
|
||||
if (!isClass(type)) {
|
||||
return false;
|
||||
}
|
||||
return type.shared.name === 'CVolatile' || type.shared.fullName === 't.CVolatile';
|
||||
}
|
||||
|
||||
// Viper 扩展:检测类型是否为 `t.CAttr`(`__attribute__` 属性标记)。
|
||||
// 注解里的属性标记(`T | t.attr.section(".x")`)与 `__attribute__(...) T` 修饰器
|
||||
// 都求值为 CAttr 实例:与 CConst / CVolatile 同族的修饰符——只携带编译期元数据,
|
||||
// 不参与类型检查(运算/下标/赋值按被修饰的基础类型处理),展示时也不作为联合成员。
|
||||
export function isViperAttrMarkerType(type: Type): boolean {
|
||||
if (!isClass(type)) {
|
||||
return false;
|
||||
}
|
||||
return type.shared.name === 'CAttr' || type.shared.fullName === 't.CAttr';
|
||||
}
|
||||
|
||||
// Viper 扩展:检测类型是否为 C 修饰符/描述符(编译时元指令,非数据类型)。
|
||||
// 这些类型在联合类型中应被过滤,只保留实际数据类型进行类型检查。
|
||||
// 依据 t.py 中 position 属性:含 PREFIX 的为修饰符(存储类/类型限定符/前缀)。
|
||||
// 特例:CDefine/CPass 虽 position=BASE,但语义是编译时元指令,也归为修饰符。
|
||||
// 注意:CUnsigned/CUnsignedChar 等是数据类型(position=BASE),不是修饰符。
|
||||
// 修饰符列表:CInline, CStatic, CConst, CExtern, CVolatile, CRegister,
|
||||
// CExport, CAuto, CPass, CDefine, State(State 等价 CExport | CExtern)
|
||||
// CExport, CAuto, CPass, CDefine, State(State 等价 CExport | CExtern),
|
||||
// CAttr(`__attribute__` 属性标记,见 isViperAttrMarkerType)
|
||||
export function isCModifierType(type: Type): boolean {
|
||||
if (!isClass(type)) {
|
||||
return false;
|
||||
@@ -3322,7 +3344,8 @@ export function isCModifierType(type: Type): boolean {
|
||||
name === 'CAuto' ||
|
||||
name === 'CPass' ||
|
||||
name === 'CDefine' ||
|
||||
name === 'State'
|
||||
name === 'State' ||
|
||||
name === 'CAttr'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4204,6 +4227,7 @@ export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): T
|
||||
!isCArrayType(subtype) &&
|
||||
!isCConstType(subtype) &&
|
||||
!isCStaticType(subtype) &&
|
||||
!isCVolatileType(subtype) &&
|
||||
!isCDefineType(subtype) &&
|
||||
!ClassType.isBuiltIn(subtype, 'object')
|
||||
) {
|
||||
|
||||
@@ -79,6 +79,7 @@ import { appendArray } from '../common/collectionUtils';
|
||||
import { ExecutionEnvironment } from '../common/configOptions';
|
||||
import * as debug from '../common/debug';
|
||||
import { fail } from '../common/debug';
|
||||
import { IsViperDialect } from '../common/dialect';
|
||||
import { ProgramView } from '../common/extensibility';
|
||||
import { fromLSPAny, toLSPAny } from '../common/lspUtils';
|
||||
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
|
||||
@@ -137,6 +138,7 @@ import {
|
||||
import { DocumentSymbolCollector } from './documentSymbolCollector';
|
||||
import { getImportFromTarget, getModuleNameCompletionSuggestions } from './importStatementCandidates';
|
||||
import { getAutoImportText, getDocumentationPartsForTypeAndDecl } from './tooltipUtils';
|
||||
import { findViperAttributeContext, getViperAttributeSymbols } from './viperAttributeUtils';
|
||||
|
||||
namespace Keywords {
|
||||
const base: string[] = [
|
||||
@@ -184,7 +186,39 @@ namespace Keywords {
|
||||
|
||||
const python3_15: string[] = [...python3_10, 'lazy'];
|
||||
|
||||
export function forVersion(version: PythonVersion): string[] {
|
||||
// Viper 扩展:Viper 方言专有关键字(Vipeye 仅支持 Viper,故一律并入补全)。
|
||||
// CPython 的关键字表里没有这些词,但它们在 Viper 里都是合法的语句/声明前缀,
|
||||
// 不并入就会"看不见"(如 define/typedef/const 等):
|
||||
// - 硬关键字:define(宏函数,等价 def)、const / volatile(类型描述符前缀);
|
||||
// - 软关键字:typedef(类型别名);inline / export / extern / virtual /
|
||||
// novirtual(C 风格修饰);enum / rnum / union / struct(类/枚举声明前缀);
|
||||
// fn / fun / func / function(def 别名)。
|
||||
// - `__attribute__`:宏属性装饰器/注解修饰器(`@__attribute__(...)` /
|
||||
// `a: __attribute__(...) T`),非关键字(普通标识符),但写法特殊,
|
||||
// 单独键入 `__a` 时若不列出就"看不见"。
|
||||
const viper: string[] = [
|
||||
'define',
|
||||
'const',
|
||||
'volatile',
|
||||
'typedef',
|
||||
'inline',
|
||||
'export',
|
||||
'extern',
|
||||
'virtual',
|
||||
'novirtual',
|
||||
'enum',
|
||||
'rnum',
|
||||
'union',
|
||||
'struct',
|
||||
'fn',
|
||||
'fun',
|
||||
'func',
|
||||
'function',
|
||||
'__attribute__',
|
||||
];
|
||||
|
||||
/** 版本对应的 CPython 关键字。 */
|
||||
function versionKeywords(version: PythonVersion): string[] {
|
||||
if (PythonVersion.isGreaterOrEqualTo(version, pythonVersion3_15)) {
|
||||
return python3_15;
|
||||
}
|
||||
@@ -197,6 +231,10 @@ namespace Keywords {
|
||||
return base;
|
||||
}
|
||||
|
||||
export function forVersion(version: PythonVersion): string[] {
|
||||
return [...versionKeywords(version), ...viper];
|
||||
}
|
||||
|
||||
// Keywords that can only begin a statement and can never begin an expression,
|
||||
// so they are suppressed in the expression-only slots recognized here. (Some
|
||||
// of these, like `from`, can still appear *within* an expression form such as
|
||||
@@ -1300,6 +1338,15 @@ export class CompletionProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Viper 扩展:`__attribute__(...)` 括号内提示属性名(t.attr 的属性方法)。
|
||||
// 必须在常规 parse 树分派之前:`__attribute__(` 前缀会脱糖为
|
||||
// `t.attr.X | 内层类型`,空实参时该节点不保留在 parse 树里,
|
||||
// 光标位置没有可命中的节点,走常规流程会返回无关补全。
|
||||
const attributeCompletions = this._getViperAttributeCompletions(offset);
|
||||
if (attributeCompletions) {
|
||||
return attributeCompletions;
|
||||
}
|
||||
|
||||
let node = ParseTreeUtils.findNodeByOffset(this.parseResults.parserOutput.parseTree, offset);
|
||||
|
||||
// See if we're inside a string literal or an f-string statement.
|
||||
@@ -3471,6 +3518,48 @@ export class CompletionProvider {
|
||||
});
|
||||
}
|
||||
|
||||
// Viper 扩展:光标位于 `__attribute__(...)` 括号内时,提示 `t.attr` 的属性名
|
||||
// (section / aligned / always_inline 等;与 TPC/TPV 的 `_MakeTAttrMarker` 一致,
|
||||
// 实参只用无前缀属性名,故这里只列 t.attr 直接的属性方法,不含 llvm 描述符类)。
|
||||
// 上下文识别与 t.attr 查找见 viperAttributeUtils(与 hoverProvider 共用)。
|
||||
private _getViperAttributeCompletions(offset: number): CompletionMap | undefined {
|
||||
const moduleNode = this.parseResults.parserOutput.parseTree;
|
||||
if (!IsViperDialect(AnalyzerNodeInfo.getFileInfo(moduleNode).executionEnvironment.dialect)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!findViperAttributeContext(this._fileContents, offset)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const attributeInfo = getViperAttributeSymbols(this.evaluator, moduleNode);
|
||||
if (!attributeInfo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 光标所在行的光标前文本,用于过滤已输入的部分。
|
||||
const lineTextRange = this.parseResults.tokenizerOutput.lines.getItemAt(this.position.line);
|
||||
const textOnLine = this._fileContents.substr(lineTextRange.start, lineTextRange.length);
|
||||
const priorText = textOnLine.substr(0, this.position.character);
|
||||
const priorWordIndex = priorText.search(/[\p{L}\p{N}\p{Pc}\p{Mn}\p{Mc}]+$/u);
|
||||
const priorWord = priorWordIndex >= 0 ? priorText.substr(priorWordIndex) : '';
|
||||
|
||||
const completionMap = new CompletionMap();
|
||||
attributeInfo.symbolTable.forEach((symbol, name) => {
|
||||
if (completionMap.has(name)) {
|
||||
return;
|
||||
}
|
||||
this.addSymbol(name, symbol, priorWord, completionMap, {
|
||||
boundObjectOrClass: attributeInfo.attrClass,
|
||||
declaredOnBoundObjectOrClass: true,
|
||||
funcParensDisabled: false,
|
||||
extraCommitChars: !!priorWord,
|
||||
});
|
||||
});
|
||||
|
||||
return completionMap;
|
||||
}
|
||||
|
||||
private _addSymbols(node: ParseNode, priorWord: string, completionMap: CompletionMap) {
|
||||
let curNode: ParseNode | undefined = node;
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@ import {
|
||||
getToolTipForType,
|
||||
getTypeForToolTip,
|
||||
getWrappedFunctionType,
|
||||
} from './tooltipUtils';
|
||||
} from './tooltipUtils';
|
||||
import { findViperAttributeContext, getViperAttributeSymbols } from './viperAttributeUtils';
|
||||
|
||||
export interface HoverTextPart {
|
||||
// 该部分按代码块渲染(markdown 下加代码围栏,见 convertHoverResults)。
|
||||
@@ -435,6 +436,49 @@ export class HoverProvider {
|
||||
return this._evaluator.printType(type, this._getPrintTypeOptions(options));
|
||||
}
|
||||
|
||||
// Viper 扩展:光标位于 `__attribute__(...)` 实参括号内时,列出该修饰器可用的
|
||||
// 全部属性名(section / aligned / always_inline / weak 等)。
|
||||
// 上下文识别与 `t.attr` 成员查找复用 viperAttributeUtils,与括号内补全同源。
|
||||
private _getViperAttributeHover(offset: number): HoverResults | null {
|
||||
if (!this._parseResults) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const moduleNode = this._parseResults.parserOutput.parseTree;
|
||||
if (!IsViperDialect(getFileInfo(moduleNode).executionEnvironment.dialect)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const context = findViperAttributeContext(this._parseResults.text, offset);
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const attributeInfo = getViperAttributeSymbols(this._evaluator, moduleNode);
|
||||
if (!attributeInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const names = Array.from(attributeInfo.symbolTable.keys()).sort();
|
||||
|
||||
return {
|
||||
parts: [
|
||||
{ text: '`__attribute__(...)` 可用属性名:' },
|
||||
{ text: names.join(' '), python: true },
|
||||
{
|
||||
text:
|
||||
'\n\n带参数者写成调用,如 `section(".text.wo")` / `aligned(16)` / ' +
|
||||
'`format(printf, 1, 2)`;无参数者可直接写属性名(`packed`)或写调用(`packed()`)。' +
|
||||
'\n\n属性清单来自 `t.attr`(TransPyC 的 `lib/includes/t.py`)。',
|
||||
},
|
||||
],
|
||||
range: {
|
||||
start: convertOffsetToPosition(context.nameStart, this._parseResults.tokenizerOutput.lines),
|
||||
end: convertOffsetToPosition(context.openParen + 1, this._parseResults.tokenizerOutput.lines),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private _getHoverResult(): HoverResults | null {
|
||||
throwIfCancellationRequested(this._token);
|
||||
|
||||
@@ -447,6 +491,14 @@ export class HoverProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Viper 扩展:光标位于 `__attribute__(...)` 实参括号内时,提示可用的全部属性名。
|
||||
// 必须在常规 parse 树分派之前:`__attribute__(...)` 脱糖后原始调用节点不保留在
|
||||
// parse 树中,光标位置(尤其实参为空/未写完时)没有可命中的节点。
|
||||
const attributeHover = this._getViperAttributeHover(offset);
|
||||
if (attributeHover) {
|
||||
return attributeHover;
|
||||
}
|
||||
|
||||
const node = ParseTreeUtils.findNodeByOffset(this._parseResults.parserOutput.parseTree, offset);
|
||||
if (node === undefined) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* viperAttributeUtils.ts
|
||||
*
|
||||
* Viper `__attribute__(...)` 的共享工具:文本上下文识别 + `t.attr` 属性成员查找。
|
||||
*
|
||||
* 由 completionProvider(括号内属性名补全)与 hoverProvider(括号内属性名提示)共用,
|
||||
* 避免两处各写一份 `t.attr` 查找逻辑导致口径漂移。
|
||||
*
|
||||
* 为什么用文本扫描定位上下文:`__attribute__(...)` 在 parser 里会被脱糖
|
||||
* (装饰器 → `@c.Attribute(t.attr.X, ...)`;注解修饰器 → `t.attr.X | 内层类型`),
|
||||
* 原始调用节点不保留在 parse 树中,光标位置(尤其实参为空或拼写未完成时)
|
||||
* 没有可命中的节点。
|
||||
*/
|
||||
|
||||
import { ScopeType } from '../analyzer/scope';
|
||||
import { getScopeForNode } from '../analyzer/scopeUtils';
|
||||
import { Symbol, SymbolTable } from '../analyzer/symbol';
|
||||
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
|
||||
import { ClassType, isFunctionOrOverloaded, isInstantiableClass, isModule, ModuleType } from '../analyzer/types';
|
||||
import { ParseNode } from '../parser/parseNodes';
|
||||
|
||||
export interface ViperAttributeContext {
|
||||
/** `__attribute__` 名字的起始 offset(用于 hover 范围锚点)。 */
|
||||
nameStart: number;
|
||||
/** `__attribute__` 名字之后 `(` 的 offset(光标必须在其右侧才算在括号内)。 */
|
||||
openParen: number;
|
||||
}
|
||||
|
||||
// 判断 `offset` 是否位于 `__attribute__(...)` 的实参括号内,是则返回上下文。
|
||||
// 扫描方式:从光标向前找最近的未闭合 `(`(跳过已输入的属性名/实参),
|
||||
// 该 `(` 左侧(允许空白分隔)的标识符必须是 `__attribute__`。
|
||||
// 遇到行尾 / `;`(深度为 0 时)即停止,避免跨语句误判。
|
||||
export function findViperAttributeContext(fileContents: string, offset: number): ViperAttributeContext | undefined {
|
||||
let depth = 0;
|
||||
let openParen = -1;
|
||||
|
||||
for (let i = offset - 1; i >= 0; i--) {
|
||||
const curChar = fileContents[i];
|
||||
if (curChar === ')') {
|
||||
depth++;
|
||||
} else if (curChar === '(') {
|
||||
if (depth === 0) {
|
||||
openParen = i;
|
||||
break;
|
||||
}
|
||||
depth--;
|
||||
} else if ((curChar === '\n' || curChar === ';') && depth === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (openParen < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// `(` 左侧(允许空白分隔)必须是 `__attribute__`。
|
||||
let nameEnd = openParen;
|
||||
while (nameEnd > 0 && /\s/.test(fileContents[nameEnd - 1])) {
|
||||
nameEnd--;
|
||||
}
|
||||
let nameStart = nameEnd;
|
||||
while (nameStart > 0 && /[\p{L}\p{N}_]/u.test(fileContents[nameStart - 1])) {
|
||||
nameStart--;
|
||||
}
|
||||
if (fileContents.slice(nameStart, nameEnd) !== '__attribute__') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { nameStart, openParen };
|
||||
}
|
||||
|
||||
// 取 `t.attr` 类**自身声明**的属性方法(只保留函数成员,排除嵌套的 llvm 描述符类等)。
|
||||
// 返回 undefined 表示没有可用的 `t` 模块 / `attr` 类(如源码未 `import t`)。
|
||||
// 只用 attr 类自身的符号表(不合并 MRO),避免把 CType/object 的继承成员
|
||||
// (__init__ / __or__ 等)当成属性名。
|
||||
export function getViperAttributeSymbols(
|
||||
evaluator: TypeEvaluator,
|
||||
scopeNode: ParseNode
|
||||
): { symbolTable: SymbolTable; attrClass: ClassType } | undefined {
|
||||
const scope = getScopeForNode(scopeNode);
|
||||
if (!scope || scope.type !== ScopeType.Module) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tSymbol = scope.lookUpSymbol('t');
|
||||
if (!tSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
const tType = evaluator.getEffectiveTypeOfSymbol(tSymbol);
|
||||
if (!isModule(tType)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const attrSymbol = ModuleType.getField(tType, 'attr');
|
||||
if (!attrSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
const attrType = evaluator.getEffectiveTypeOfSymbol(attrSymbol);
|
||||
if (!isInstantiableClass(attrType)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const symbolTable: SymbolTable = new Map<string, Symbol>();
|
||||
ClassType.getSymbolTable(attrType).forEach((symbol, name) => {
|
||||
if (isFunctionOrOverloaded(evaluator.getEffectiveTypeOfSymbol(symbol))) {
|
||||
symbolTable.set(name, symbol);
|
||||
}
|
||||
});
|
||||
|
||||
return { symbolTable, attrClass: attrType };
|
||||
}
|
||||
@@ -619,6 +619,14 @@ export namespace Localizer {
|
||||
new ParameterizedString<{ name: string }>(
|
||||
getRawString('Diagnostic.viperGlobalAssignmentWithoutGlobal')
|
||||
);
|
||||
export const viperGlobalRedeclarationWithoutGlobal = () =>
|
||||
new ParameterizedString<{ name: string }>(
|
||||
getRawString('Diagnostic.viperGlobalRedeclarationWithoutGlobal')
|
||||
);
|
||||
export const viperGlobalRedeclarationWithGlobal = () =>
|
||||
new ParameterizedString<{ name: string }>(
|
||||
getRawString('Diagnostic.viperGlobalRedeclarationWithGlobal')
|
||||
);
|
||||
export const importSymbolUnknown = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.importSymbolUnknown'));
|
||||
export const incompatibleMethodOverride = () =>
|
||||
@@ -1189,6 +1197,15 @@ export namespace Localizer {
|
||||
export const unexpectedUnindent = () => getRawString('Diagnostic.unexpectedUnindent');
|
||||
export const viperModifierRequiresDef = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperModifierRequiresDef'));
|
||||
export const viperTypeDescriptorRequiresType = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperTypeDescriptorRequiresType'));
|
||||
export const viperVolatileValueRequiresCall = () => getRawString('Diagnostic.viperVolatileValueRequiresCall');
|
||||
export const viperVirtualModifierRequiresClassOrDef = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperVirtualModifierRequiresClassOrDef'));
|
||||
export const viperVirtualDefPlacement = () => getRawString('Diagnostic.viperVirtualDefPlacement');
|
||||
export const viperNoVirtualDefPlacement = () => getRawString('Diagnostic.viperNoVirtualDefPlacement');
|
||||
export const viperVirtualDefInNoVirtualClass = () =>
|
||||
getRawString('Diagnostic.viperVirtualDefInNoVirtualClass');
|
||||
export const viperExternBodyInvalid = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperExternBodyInvalid'));
|
||||
export const viperLenNoLenMethod = () =>
|
||||
|
||||
@@ -657,6 +657,8 @@
|
||||
"viperUnresolvedImportName": "Unresolved import name \"{name}\"",
|
||||
"viperUnknownMemberAccess": "Cannot access member \"{memberName}\" on unresolved type, object may be undefined",
|
||||
"viperGlobalAssignmentWithoutGlobal": "Global variable \"{name}\" requires global declaration when writing inside a function",
|
||||
"viperGlobalRedeclarationWithoutGlobal": "Duplicate declaration: \"{name}\" is already a module-level variable; inside a function it must not be declared again with a type annotation when no global statement is present (it would shadow the global). Rename it for an independent local variable, or add a global statement and drop this annotation to modify the global",
|
||||
"viperGlobalRedeclarationWithGlobal": "Duplicate declaration: \"{name}\" is already a module-level variable and is declared again with a type annotation after a global/nonlocal statement. Drop this annotation and assign directly to modify the global",
|
||||
"importSymbolUnknown": "\"{name}\" is unknown import symbol",
|
||||
"incompatibleMethodOverride": "Method \"{name}\" overrides class \"{className}\" in an incompatible manner",
|
||||
"inconsistentIndent": "Unindent amount does not match previous indent",
|
||||
@@ -1628,6 +1630,12 @@
|
||||
"unexpectedIndent": "Unexpected indentation",
|
||||
"unexpectedUnindent": "Unindent not expected",
|
||||
"viperModifierRequiresDef": "The modifier \"{name}\" must be used with 'def'",
|
||||
"viperTypeDescriptorRequiresType": "The type descriptor \"{name}\" must be followed by a type (e.g. a: {name} int)",
|
||||
"viperVolatileValueRequiresCall": "The volatile value prefix can only modify a call (e.g. a: int = volatile f() / a: int = volatile Struct())",
|
||||
"viperVirtualModifierRequiresClassOrDef": "The modifier \"{name}\" must be used with 'class' or 'def'",
|
||||
"viperVirtualDefPlacement": "'virtual def' must appear inside a 'class' or 'virtual class'",
|
||||
"viperNoVirtualDefPlacement": "'novirtual def' must appear inside a 'class', 'virtual class' or 'novirtual class'",
|
||||
"viperVirtualDefInNoVirtualClass": "'virtual def' cannot appear inside a 'novirtual class' (this class form emits no vtable)",
|
||||
"viperExternBodyInvalid": "The extern function \"{name}\" must have a body of only 'pass' or 'None'",
|
||||
"viperLenNoLenMethod": "Type \"{name}\" has no __len__ method and cannot be used with len()",
|
||||
"unhashableDictKey": "Dictionary key must be hashable",
|
||||
|
||||
@@ -272,6 +272,8 @@
|
||||
"viperUnresolvedImportName": "无法解析的导入名称“{name}”",
|
||||
"viperUnknownMemberAccess": "无法解析类型上的成员“{memberName}”访问,对象可能未定义",
|
||||
"viperGlobalAssignmentWithoutGlobal": "全局变量“{name}”在函数内写入需要 global 声明",
|
||||
"viperGlobalRedeclarationWithoutGlobal": "重复声明:“{name}”已是模块级变量,函数内未声明 global 时不得再用类型注解声明同名变量(会把全局遮蔽为局部)。如需独立局部变量请换名;如需修改全局请加 global 声明并删除本行注解",
|
||||
"viperGlobalRedeclarationWithGlobal": "重复声明:“{name}”已是模块级变量,此处声明 global/nonlocal 之后又用类型注解重新声明。请删除本行的类型注解,直接赋值即可修改全局",
|
||||
"importSymbolUnknown": "“{name}”是未知的导入符号",
|
||||
"incompatibleMethodOverride": "方法“{name}”以不兼容的方式替代类“{className}”",
|
||||
"inconsistentIndent": "取消缩进量与以前的缩进不匹配",
|
||||
@@ -620,6 +622,12 @@
|
||||
"unexpectedIndent": "意外缩进",
|
||||
"unexpectedUnindent": "不应取消缩进",
|
||||
"viperModifierRequiresDef": "修饰关键字 \"{name}\" 必须与 def 搭配使用",
|
||||
"viperTypeDescriptorRequiresType": "类型描述符 \"{name}\" 后必须跟类型(如 a: {name} int)",
|
||||
"viperVolatileValueRequiresCall": "volatile 值前缀只能修饰调用(如 a: int = volatile f() / a: int = volatile Struct())",
|
||||
"viperVirtualModifierRequiresClassOrDef": "修饰关键字 \"{name}\" 必须与 class 或 def 搭配使用",
|
||||
"viperVirtualDefPlacement": "\"virtual def\" 必须位于 class 或 virtual class 内",
|
||||
"viperNoVirtualDefPlacement": "\"novirtual def\" 必须位于 class、virtual class 或 novirtual class 内",
|
||||
"viperVirtualDefInNoVirtualClass": "\"virtual def\" 不能位于 novirtual class 内(该形态的类不生成 vtable)",
|
||||
"viperExternBodyInvalid": "extern 函数 \"{name}\" 的函数体必须为 pass 或 None",
|
||||
"viperLenNoLenMethod": "类型 \"{name}\" 没有 __len__ 方法,不能用于 len()",
|
||||
"unhashableDictKey": "字典密钥必须可哈希",
|
||||
|
||||
@@ -572,6 +572,12 @@ export interface ClassNode extends ParseNodeBase<ParseNodeType.Class> {
|
||||
// Viper 扩展:由 `enum X:` 语法声明(等价 `class X(t.CEnum):`)。
|
||||
// 成员可以只写成员名(裸名 / 逗号分隔的裸名),值由编译期推导。
|
||||
isViperEnum: boolean;
|
||||
|
||||
// Viper 扩展:类声明前的 vtable 修饰关键字(`virtual class X` / `novirtual class X`)。
|
||||
// 'virtual' 等价 `@t.CVTable class X`(启用多态 vtable);
|
||||
// 'novirtual' 等价 `@t.NoVTable class X`(非多态,字段展平嵌入)。
|
||||
// 未写修饰时为 undefined(普通 class)。
|
||||
viperClassModifier?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1691,6 +1697,13 @@ export interface NameNode extends ParseNodeBase<ParseNodeType.Name> {
|
||||
// `T1: t.CTypedef = T2` 相同,但本写法不要求源码 import t,
|
||||
// 故合成 `t` 解析失败时同样静默退化为 Unknown(不报"未定义 t")。
|
||||
isViperTypedef?: boolean;
|
||||
|
||||
// Viper 扩展:类型描述符 `const T` / `volatile T` 脱糖
|
||||
// (见 parser._parseViperTypeDescriptorPrefix)时合成出来的 `CConst` /
|
||||
// `CVolatile` 名:零长度、不对应源码字符。语义与手写 `t.CConst | T` 相同,
|
||||
// 但脱糖目标是 builtins stub 中的别名,不要求源码 import t;
|
||||
// 名字解析不到时静默退化为 Unknown。
|
||||
isViperTypeDescriptor?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -251,6 +251,17 @@ export class Parser {
|
||||
// Viper 扩展:正在解析 `[SIZE]T` 的元素类型(此时即使身处下标实参内,
|
||||
// 也允许再出现 `[SIZE]T`,使 `list[[2][3]int]` 这类嵌套合法)。
|
||||
private _isParsingViperBracketArrayElement = false;
|
||||
// Viper 扩展:正在解析类型描述符(const / volatile)修饰的主体类型。
|
||||
// 此时禁止在表达式层就地消费 C 指针后缀 `*`,使其统一冒泡到最外层
|
||||
// _parseTypeAnnotation 应用 —— 保证 `const T*` 恒为 `(CConst | T)*`,
|
||||
// 不因 `*` 之后是行尾还是下一行内容而在两条解析路径间摇摆
|
||||
// (见 _isAtViperPointerTypeSuffix 对换行的处理)。
|
||||
private _isParsingViperTypeDescriptorInner = false;
|
||||
// Viper 扩展:当前所处的类声明形态,用于校验 vtable 修饰方法的出现位置:
|
||||
// `virtual def` 仅允许位于 class / virtual class 内;
|
||||
// `novirtual def` 允许位于 class / virtual class / novirtual class 内。
|
||||
// 进入函数体(suite)时置为 undefined:方法体内的 def 不是类成员。
|
||||
private _viperEnclosingClassKind: 'plain' | 'virtual' | 'novirtual' | undefined = undefined;
|
||||
private _isParsingQuotedText = false;
|
||||
private _isInFinallyBlock = false;
|
||||
private _isInFinallyLoop = false;
|
||||
@@ -443,6 +454,38 @@ export class Parser {
|
||||
modifierToken.start,
|
||||
modifierToken.start + modifierToken.length
|
||||
);
|
||||
// Viper 扩展:`virtual class X` / `novirtual class X`(类级 vtable 标记),
|
||||
// `virtual def f(...)` / `novirtual def f(...)`(方法级 vtable 标记)。
|
||||
// 语义等价 `@t.CVTable class X` / `@t.NoVTable class X`(见 t.vpi),
|
||||
// 仅记录在节点上供编译器使用;类型层无额外语义。
|
||||
// 必须在下方通用「修饰关键字 + def」分支之前,否则方法级修饰的
|
||||
// 位置校验(virtual def 不得在 novirtual class 内 / 类外)会被跳过。
|
||||
if (modifierText === 'virtual' || modifierText === 'novirtual') {
|
||||
const nextKeywordType =
|
||||
this._peekToken(1).type === TokenType.Keyword
|
||||
? (this._peekToken(1) as KeywordToken).keywordType
|
||||
: undefined;
|
||||
if (nextKeywordType === KeywordType.Class) {
|
||||
this._getNextToken();
|
||||
return this._parseClassDef(/* decorators */ undefined, modifierText);
|
||||
}
|
||||
if (nextKeywordType === KeywordType.Def) {
|
||||
this._validateViperVirtualMethodPlacement(modifierText, modifierToken);
|
||||
this._getNextToken();
|
||||
// 不传 defTokenOverride:`def` 关键字仍须由 _parseFunctionDef 消费。
|
||||
return this._parseFunctionDef(
|
||||
/* asyncToken */ undefined,
|
||||
/* decorators */ undefined,
|
||||
modifierText
|
||||
);
|
||||
}
|
||||
this._addSyntaxError(
|
||||
LocMessage.viperVirtualModifierRequiresClassOrDef().format({ name: modifierText }),
|
||||
modifierToken
|
||||
);
|
||||
this._getNextToken();
|
||||
return this._parseSimpleStatement();
|
||||
}
|
||||
if (
|
||||
this._peekToken(1).type === TokenType.Keyword &&
|
||||
(this._peekToken(1) as KeywordToken).keywordType === KeywordType.Def
|
||||
@@ -465,6 +508,20 @@ export class Parser {
|
||||
modifierToken
|
||||
);
|
||||
}
|
||||
// Viper 扩展:`inline while cond:` / `inline for x in y:`(内联循环标记)。
|
||||
// 内联是代码生成期的提示,类型层无额外语义,此处仅消费修饰关键字并
|
||||
// 按普通 while / for 语句解析(高亮由 TextMate grammar 负责)。
|
||||
if (modifierText === 'inline' && this._peekToken(1).type === TokenType.Keyword) {
|
||||
const nextKeywordType = (this._peekToken(1) as KeywordToken).keywordType;
|
||||
if (nextKeywordType === KeywordType.While) {
|
||||
this._getNextToken();
|
||||
return this._parseWhileStatement();
|
||||
}
|
||||
if (nextKeywordType === KeywordType.For) {
|
||||
this._getNextToken();
|
||||
return this._parseForStatement();
|
||||
}
|
||||
}
|
||||
this._addSyntaxError(
|
||||
LocMessage.viperModifierRequiresDef().format({ name: modifierText }),
|
||||
modifierToken
|
||||
@@ -2213,11 +2270,18 @@ export class Parser {
|
||||
extendRange(suite, this._tokenizerOutput!.tokens.getItemAt(this._tokenIndex - 1));
|
||||
}
|
||||
} else {
|
||||
// Viper 扩展:函数体内 `virtual def` / `novirtual def` 不是类成员,
|
||||
// 解析函数体期间清空所处的类形态,使嵌套 def 的位置校验按"不在类内"处理。
|
||||
const wasEnclosingClassKind = this._viperEnclosingClassKind;
|
||||
this._viperEnclosingClassKind = undefined;
|
||||
|
||||
suite = this._parseSuite(/* isFunction */ true, this._parseOptions.skipFunctionAndClassBody, () => {
|
||||
if (!functionTypeAnnotationToken) {
|
||||
functionTypeAnnotationToken = this._getTypeAnnotationCommentText();
|
||||
}
|
||||
});
|
||||
|
||||
this._viperEnclosingClassKind = wasEnclosingClassKind;
|
||||
}
|
||||
|
||||
this._isInExceptionGroup = wasInExceptionGroup;
|
||||
@@ -2611,6 +2675,35 @@ export class Parser {
|
||||
return this._parseFunctionDef(undefined, decoratorList);
|
||||
} else if (nextToken.keywordType === KeywordType.Class) {
|
||||
return this._parseClassDef(decoratorList);
|
||||
} else if (nextToken.keywordType === KeywordType.ViperModifier) {
|
||||
// Viper 扩展:装饰器之后的 vtable 修饰(如 `@Foo` + `virtual class X`
|
||||
// / `virtual def f(...)`)。与 _parseStatement 的同名分支保持一致。
|
||||
const modifierToken = this._peekToken();
|
||||
const modifierText = this._fileContents!.slice(
|
||||
modifierToken.start,
|
||||
modifierToken.start + modifierToken.length
|
||||
);
|
||||
if (modifierText === 'virtual' || modifierText === 'novirtual') {
|
||||
const afterModifierKeywordType =
|
||||
this._peekToken(1).type === TokenType.Keyword
|
||||
? (this._peekToken(1) as KeywordToken).keywordType
|
||||
: undefined;
|
||||
if (afterModifierKeywordType === KeywordType.Class) {
|
||||
this._getNextToken();
|
||||
return this._parseClassDef(decoratorList, modifierText);
|
||||
}
|
||||
if (afterModifierKeywordType === KeywordType.Def) {
|
||||
this._validateViperVirtualMethodPlacement(modifierText, modifierToken);
|
||||
this._getNextToken();
|
||||
return this._parseFunctionDef(undefined, decoratorList, modifierText);
|
||||
}
|
||||
this._addSyntaxError(
|
||||
LocMessage.viperVirtualModifierRequiresClassOrDef().format({ name: modifierText }),
|
||||
modifierToken
|
||||
);
|
||||
}
|
||||
// 其他修饰关键字(inline/export/extern)不在装饰器后支持,
|
||||
// 保持原行为:落到下方 expectedAfterDecorator 报错。
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2646,7 +2739,14 @@ export class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
const decoratorNode = DecoratorNode.create(atOperator, expression);
|
||||
// Viper 扩展:`@__attribute__(section(".x"), always_inline)` desugar 为
|
||||
// `@c.Attribute(t.attr.section(".x"), t.attr.always_inline)`
|
||||
// ——与 TPC/TPV 的 includes/ast/parser.vp `_DesugarMagicAttribute` 同形,
|
||||
// class 与 def 共用(装饰器链统一走本函数);其它装饰器原样返回。
|
||||
const decoratorNode = DecoratorNode.create(
|
||||
atOperator,
|
||||
this._desugarViperMagicAttribute(expression)
|
||||
);
|
||||
|
||||
if (!this._consumeTokenIfType(TokenType.NewLine)) {
|
||||
this._addSyntaxError(LocMessage.expectedDecoratorNewline(), this._peekToken());
|
||||
@@ -2681,8 +2781,28 @@ export class Parser {
|
||||
return afterName.type === TokenType.Operator && (afterName as OperatorToken).operatorType === OperatorType.Assign;
|
||||
}
|
||||
|
||||
// Viper 扩展:校验 vtable 修饰方法(`virtual def` / `novirtual def`)的出现位置。
|
||||
// - 必须位于类体内(模块级、函数体内均非法);
|
||||
// - `virtual def` 不能位于 `novirtual class` 内(该形态的类不生成 vtable)。
|
||||
private _validateViperVirtualMethodPlacement(modifierText: string, modifierToken: Token): void {
|
||||
const enclosingClassKind = this._viperEnclosingClassKind;
|
||||
if (!enclosingClassKind) {
|
||||
this._addSyntaxError(
|
||||
modifierText === 'virtual'
|
||||
? LocMessage.viperVirtualDefPlacement()
|
||||
: LocMessage.viperNoVirtualDefPlacement(),
|
||||
modifierToken
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (modifierText === 'virtual' && enclosingClassKind === 'novirtual') {
|
||||
this._addSyntaxError(LocMessage.viperVirtualDefInNoVirtualClass(), modifierToken);
|
||||
}
|
||||
}
|
||||
|
||||
// classdef: 'class' NAME ['(' [arglist] ')'] suite
|
||||
private _parseClassDef(decorators?: DecoratorNode[]): ClassNode {
|
||||
private _parseClassDef(decorators?: DecoratorNode[], viperClassModifier?: string): ClassNode {
|
||||
const classToken = this._getKeywordToken(KeywordType.Class);
|
||||
|
||||
let nameToken = this._getTokenIfIdentifier();
|
||||
@@ -2714,6 +2834,12 @@ export class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
// Viper 扩展:类体内的 vtable 修饰形态(供 `virtual def` / `novirtual def` 位置校验)。
|
||||
const enclosingClassKind: 'plain' | 'virtual' | 'novirtual' =
|
||||
viperClassModifier === 'virtual' ? 'virtual' : viperClassModifier === 'novirtual' ? 'novirtual' : 'plain';
|
||||
const wasEnclosingClassKind = this._viperEnclosingClassKind;
|
||||
this._viperEnclosingClassKind = enclosingClassKind;
|
||||
|
||||
const suite = this._parseSuite(
|
||||
/* isFunction */ false,
|
||||
this._parseOptions.skipFunctionAndClassBody,
|
||||
@@ -2721,6 +2847,8 @@ export class Parser {
|
||||
/* allowTrailingIndent */ classToken.isViperEnum === true
|
||||
);
|
||||
|
||||
this._viperEnclosingClassKind = wasEnclosingClassKind;
|
||||
|
||||
const classNode = ClassNode.create(classToken, NameNode.create(nameToken), suite, typeParameters);
|
||||
classNode.d.arguments = argList;
|
||||
argList.forEach((arg) => {
|
||||
@@ -2732,6 +2860,11 @@ export class Parser {
|
||||
classNode.d.isViperEnum = true;
|
||||
}
|
||||
|
||||
// Viper 扩展:`virtual class X` / `novirtual class X` 的修饰关键字。
|
||||
if (viperClassModifier) {
|
||||
classNode.d.viperClassModifier = viperClassModifier;
|
||||
}
|
||||
|
||||
if (decorators) {
|
||||
classNode.d.decorators = decorators;
|
||||
if (decorators.length > 0) {
|
||||
@@ -3882,7 +4015,7 @@ export class Parser {
|
||||
this._parseOptions.dialect === 'viper' &&
|
||||
nextOperator === OperatorType.Multiply
|
||||
) {
|
||||
if (this._isAtViperPointerTypeSuffix()) {
|
||||
if (!this._isParsingViperTypeDescriptorInner && this._isAtViperPointerTypeSuffix()) {
|
||||
leftExpr = this._parseViperPointerTypeSuffix(leftExpr);
|
||||
peekToken = this._peekToken();
|
||||
nextOperator = this._peekOperatorType();
|
||||
@@ -3986,6 +4119,16 @@ export class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
// Viper 扩展:值表达式中的 `volatile` 前缀 —— `a: int = volatile f()` /
|
||||
// `a: int = volatile 结构体()`。语义:该调用(函数调用或结构体构造的
|
||||
// __new__ / __before_init__ / __init__)被标记为 volatile(阻止编译器优化,
|
||||
// 加 LLVM volatile)。仅 LSP 侧解析(TPC/TPV 不处理该形态):
|
||||
// 消费 `volatile` 后必须紧跟「调用」形态,否则报语法错误;
|
||||
// `volatile = 2` 这类裸用因下一 token 非表达式起点而不进入本分支,仍照旧失败。
|
||||
if (this._isAtViperVolatileValuePrefix()) {
|
||||
return this._parseViperVolatileValue();
|
||||
}
|
||||
|
||||
let atomExpression = this._parseAtom();
|
||||
if (atomExpression.nodeType === ParseNodeType.Error) {
|
||||
return atomExpression;
|
||||
@@ -3993,6 +4136,17 @@ export class Parser {
|
||||
|
||||
// Consume trailers.
|
||||
while (true) {
|
||||
// Viper 扩展:`T*(x)` / `T**(x)` —— 星号串与基类型、'(' 均**紧贴**时按 C 风格
|
||||
// 指针强转调用解析(判据与 TPC ExprParser._AtPtrSuffixCall 同源)。
|
||||
// 先把星串消费为「指针类型后缀」,紧接的 '(' 由下一轮 trailer 按调用处理,
|
||||
// 产物与既有 `(T*)x` 逐字同形:Call(func = 标记的指针后缀, args = 实参)。
|
||||
// 因此 `T*(x)[0] = v` 这类后续下标(乃至赋值目标)自然成立——TPC 同样把
|
||||
// `T*(x)` 解析成调用,故 `uint32*(info.vendor_id)[0] = res.ebx` 是合法赋值目标。
|
||||
if (this._isAtViperPointerSuffixCastCall(atomExpression)) {
|
||||
atomExpression = this._parseViperPointerTypeSuffix(atomExpression);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is it a function call?
|
||||
const startOfTrailerToken = this._peekToken();
|
||||
if (this._consumeTokenIfType(TokenType.OpenParenthesis)) {
|
||||
@@ -4409,7 +4563,30 @@ export class Parser {
|
||||
}
|
||||
|
||||
if (nextToken.type === TokenType.Identifier) {
|
||||
return NameNode.create(this._getNextToken() as IdentifierToken);
|
||||
const identifierToken = this._getNextToken() as IdentifierToken;
|
||||
|
||||
// Viper 扩展:注解位置的裸 `callable` 等价 `Callable`(t.Callable /
|
||||
// typing.Callable),支持 `callable[[A, B], R]`、`callable[..., R]`、
|
||||
// `callable[[], R]`。**仅类型注解位置**脱糖:值位置的 `callable(x)`
|
||||
// 仍是内置函数(isinstance 风格的 TypeIs 判定)。
|
||||
// `Callable` 声明在 builtins stub 中(t.CTypedef = t.Callable),
|
||||
// 故不要求源码显式 `import t`。
|
||||
if (
|
||||
this._parseOptions.dialect === 'viper' &&
|
||||
this._isParsingTypeAnnotation &&
|
||||
identifierToken.value === 'callable'
|
||||
) {
|
||||
return NameNode.create(
|
||||
IdentifierToken.create(
|
||||
identifierToken.start,
|
||||
identifierToken.length,
|
||||
'Callable',
|
||||
identifierToken.comments
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return NameNode.create(identifierToken);
|
||||
}
|
||||
|
||||
if (nextToken.type === TokenType.String || nextToken.type === TokenType.FStringStart) {
|
||||
@@ -5122,10 +5299,13 @@ export class Parser {
|
||||
this._addSyntaxError(LocMessage.unpackedSubscriptIllegal(), startToken);
|
||||
}
|
||||
|
||||
let result = this._parseViperBracketArrayTypeIfNeeded();
|
||||
// Viper 扩展:注解主体(类型描述符前缀 / 定长数组 / 普通类型表达式)。
|
||||
let result = this._parseViperTypeAnnotationCore();
|
||||
|
||||
// Viper 扩展:C 指针类型后缀 T* / T** / T***(须在还原 _isParsingTypeAnnotation 之前,
|
||||
// 使 _parseArithmeticTerm/_parseArithmeticFactor 的星号守卫仍生效)。
|
||||
// 后缀统一在最外层应用,故 `const int*` 得到 `(CConst | int)*`
|
||||
// (指针指向被描述符修饰的类型),与 `int*` 中 `*` 绑定到类型的位置一致。
|
||||
result = this._parseViperPointerTypeSuffix(result);
|
||||
|
||||
if (isUnpack) {
|
||||
@@ -5138,6 +5318,302 @@ export class Parser {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Viper 扩展:注解主体 = 类型描述符前缀(const / volatile)+ 类型表达式。
|
||||
// 与 _parseTypeAnnotation 分离,使 C 指针后缀 `*` 由调用方在最外层统一应用:
|
||||
// `const int*` 的脱糖结果 `CConst | int` 先成形,再整体加一层指针后缀。
|
||||
private _parseViperTypeAnnotationCore(): ExpressionNode {
|
||||
const attributeNode = this._parseViperAttributePrefix();
|
||||
if (attributeNode) {
|
||||
return attributeNode;
|
||||
}
|
||||
const descriptorNode = this._parseViperTypeDescriptorPrefix();
|
||||
if (descriptorNode) {
|
||||
return descriptorNode;
|
||||
}
|
||||
return this._parseViperBracketArrayTypeIfNeeded();
|
||||
}
|
||||
|
||||
// Viper 扩展:注解位置的 `__attribute__(...)` 修饰器前缀。
|
||||
// 解析层脱糖为等价的联合类型注解,与手写 `T | t.attr.section(".x")` 的 AST 同形
|
||||
// (编译器按 AST 扫描属性:TPC HandlesAssign._ExtractGlobalAttrs /
|
||||
// TPV TranslatorGenMixin._scan_global_tattr),从而完全复用既有属性标记语义:
|
||||
// __attribute__(section(".x")) const int → (t.attr.section(".x") | (CConst | int))
|
||||
// __attribute__(a, b) int → ((t.attr.a | t.attr.b) | int)
|
||||
// 与 const / volatile 同族:只作修饰,不改变数据类型。
|
||||
// 语法与装饰器形态一致(实参用无前缀属性名,逐项脱糖为 `t.attr.<name>`;
|
||||
// 已是 `t.attr.X` / `c.Attribute(...)` 形态的实参原样保留,允许混写)。
|
||||
// 注意:与 GCC 的 `__attribute__((a, b))` 不同,Viper 只用**单层**括号。
|
||||
private _parseViperAttributePrefix(): ExpressionNode | undefined {
|
||||
if (this._parseOptions.dialect !== 'viper' || !this._isAtViperAttributePrefix(0)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const attributeToken = this._peekToken();
|
||||
this._getNextToken(); // 消费 `__attribute__`
|
||||
this._getNextToken(); // 消费 '('
|
||||
|
||||
const argListResult = this._parseArgList();
|
||||
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
|
||||
this._addSyntaxError(LocMessage.expectedCloseParen(), this._peekToken());
|
||||
this._consumeTokensUntilType([TokenType.NewLine]);
|
||||
}
|
||||
|
||||
const markers: ExpressionNode[] = [];
|
||||
argListResult.args.forEach((arg) => {
|
||||
if (arg.d.argCategory === ArgCategory.Simple) {
|
||||
markers.push(this._makeViperAttrMarker(arg.d.valueExpr));
|
||||
}
|
||||
});
|
||||
|
||||
// 修饰器后必须跟类型(同 const / volatile 的硬关键字约束)。
|
||||
const nextToken = this._peekToken();
|
||||
if (!this._isViperTypeAtomStartToken(nextToken) && !this._isAtViperAttributePrefix(0)) {
|
||||
this._addSyntaxError(
|
||||
LocMessage.viperTypeDescriptorRequiresType().format({ name: '__attribute__' }),
|
||||
attributeToken
|
||||
);
|
||||
return ErrorNode.create(attributeToken, ErrorExpressionCategory.MissingExpression);
|
||||
}
|
||||
|
||||
// 递归解析被修饰的主体(支持 `__attribute__(...) const int`、
|
||||
// `const __attribute__(...) int`、多个属性前缀等任意组合)。
|
||||
const innerType = this._parseViperTypeAnnotationCore();
|
||||
|
||||
// 合成 '|' 链('|' token 零长度,源码中不存在):marker1 | marker2 | ... | inner
|
||||
let modifierNode: ExpressionNode | undefined;
|
||||
for (const marker of markers) {
|
||||
if (!modifierNode) {
|
||||
modifierNode = marker;
|
||||
continue;
|
||||
}
|
||||
modifierNode = this._createBinaryOperationNode(
|
||||
modifierNode,
|
||||
marker,
|
||||
this._createViperSyntheticBarToken(attributeToken),
|
||||
OperatorType.BitwiseOr
|
||||
);
|
||||
}
|
||||
|
||||
if (!modifierNode) {
|
||||
return innerType;
|
||||
}
|
||||
|
||||
return this._createBinaryOperationNode(
|
||||
modifierNode,
|
||||
innerType,
|
||||
this._createViperSyntheticBarToken(attributeToken),
|
||||
OperatorType.BitwiseOr
|
||||
);
|
||||
}
|
||||
|
||||
// Viper 扩展:合成零长度的 '|' 算子 token(源码中不存在,仅承载位置信息)。
|
||||
private _createViperSyntheticBarToken(anchorToken: Token): OperatorToken {
|
||||
return OperatorToken.create(
|
||||
anchorToken.start + anchorToken.length,
|
||||
0,
|
||||
OperatorType.BitwiseOr,
|
||||
/* comments */ undefined
|
||||
);
|
||||
}
|
||||
|
||||
// Viper 扩展:当前(可带 offset)是否为注解位置的 `__attribute__(` 前缀。
|
||||
// `__attribute__` 是普通标识符(非关键字),后必须紧邻 '('。
|
||||
private _isAtViperAttributePrefix(offset: number): boolean {
|
||||
const nameToken = this._peekToken(offset);
|
||||
if (nameToken.type !== TokenType.Identifier) {
|
||||
return false;
|
||||
}
|
||||
if (this._fileContents!.slice(nameToken.start, nameToken.start + nameToken.length) !== '__attribute__') {
|
||||
return false;
|
||||
}
|
||||
return this._peekToken(offset + 1).type === TokenType.OpenParenthesis;
|
||||
}
|
||||
|
||||
// Viper 扩展:把 `__attribute__(...)` 的实参脱糖为 `t.attr.<name>` 标记。
|
||||
// - `always_inline`(Name) → `t.attr.always_inline`
|
||||
// - `section(".x")`(Call,callee 是 Name)→ `t.attr.section(".x")`
|
||||
// - 已是 `t.attr.X` / `c.Attribute(...)` 形态 → 原样保留(允许混写)
|
||||
// 与 TPC/TPV 的 includes/ast/parser.vp `_MakeTAttrMarker` 逐条对应;
|
||||
// 成员名复用实参自身的 token(真实位置),故属性名上的跳转/悬停仍指向 t.vpi。
|
||||
private _makeViperAttrMarker(argExpr: ExpressionNode): ExpressionNode {
|
||||
const attrRoot = MemberAccessNode.create(
|
||||
NameNode.create(IdentifierToken.create(argExpr.start, 0, 't', /* comments */ undefined)),
|
||||
NameNode.create(IdentifierToken.create(argExpr.start, 0, 'attr', /* comments */ undefined))
|
||||
);
|
||||
|
||||
if (argExpr.nodeType === ParseNodeType.Name) {
|
||||
return MemberAccessNode.create(attrRoot, NameNode.create(argExpr.d.token));
|
||||
}
|
||||
|
||||
if (argExpr.nodeType === ParseNodeType.Call && argExpr.d.leftExpr.nodeType === ParseNodeType.Name) {
|
||||
const inner = MemberAccessNode.create(attrRoot, NameNode.create(argExpr.d.leftExpr.d.token));
|
||||
return CallNode.create(inner, argExpr.d.args, argExpr.d.trailingComma);
|
||||
}
|
||||
|
||||
// 已是属性标记形态(t.attr.X / c.Attribute(...)):原样保留。
|
||||
return argExpr;
|
||||
}
|
||||
|
||||
// Viper 扩展:把 `@__attribute__(...)` 装饰器脱糖为
|
||||
// `@c.Attribute(t.attr.a, t.attr.b)`——与 TPC/TPV 的
|
||||
// includes/ast/parser.vp `_DesugarMagicAttribute` 逐字段同形,因此 class 与 def
|
||||
// 共用同一形态;其它装饰器原样返回。
|
||||
private _desugarViperMagicAttribute(expr: ExpressionNode): ExpressionNode {
|
||||
if (this._parseOptions.dialect !== 'viper' || expr.nodeType !== ParseNodeType.Call) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
if (!this._isViperMagicAttributeCallee(expr.d.leftExpr)) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
const attributeCallee = MemberAccessNode.create(
|
||||
NameNode.create(IdentifierToken.create(expr.start, 0, 'c', /* comments */ undefined)),
|
||||
NameNode.create(IdentifierToken.create(expr.start, 0, 'Attribute', /* comments */ undefined))
|
||||
);
|
||||
|
||||
const newArgs = expr.d.args.map((arg) => {
|
||||
if (arg.d.argCategory !== ArgCategory.Simple) {
|
||||
return arg;
|
||||
}
|
||||
return ArgumentNode.create(undefined, this._makeViperAttrMarker(arg.d.valueExpr), ArgCategory.Simple);
|
||||
});
|
||||
|
||||
return CallNode.create(attributeCallee, newArgs, expr.d.trailingComma);
|
||||
}
|
||||
|
||||
// Viper 扩展:装饰器被调用者是否为 `__attribute__`(Name 形态,或 `X.__attribute__` 尾段形态)。
|
||||
private _isViperMagicAttributeCallee(node: ExpressionNode): boolean {
|
||||
if (node.nodeType === ParseNodeType.Name) {
|
||||
return node.d.value === '__attribute__';
|
||||
}
|
||||
if (node.nodeType === ParseNodeType.MemberAccess) {
|
||||
return node.d.member.d.value === '__attribute__';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Viper 扩展:判定注解位置 `const` / `volatile` / `__attribute__(` 之后是否跟类型。
|
||||
private _isViperTypeAtomStartToken(token: Token): boolean {
|
||||
return (
|
||||
token.type === TokenType.Identifier ||
|
||||
token.type === TokenType.OpenBracket ||
|
||||
(token.type === TokenType.Keyword &&
|
||||
(token as KeywordToken).keywordType === KeywordType.ViperTypeDescriptor)
|
||||
);
|
||||
}
|
||||
|
||||
// Viper 扩展:当前是否为值表达式中的 `volatile <调用>` 前缀。
|
||||
// 仅 `volatile` 有值位置用法(`const` 没有);且下一 token 必须是表达式起点
|
||||
// (标识符 / '('),否则不算(`volatile = 2`、`volatile: int` 等仍走原错误路径)。
|
||||
private _isAtViperVolatileValuePrefix(): boolean {
|
||||
if (this._parseOptions.dialect !== 'viper' || this._isParsingTypeAnnotation) {
|
||||
return false;
|
||||
}
|
||||
const descriptorToken = this._peekToken();
|
||||
if (
|
||||
descriptorToken.type !== TokenType.Keyword ||
|
||||
(descriptorToken as KeywordToken).keywordType !== KeywordType.ViperTypeDescriptor
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
this._fileContents!.slice(descriptorToken.start, descriptorToken.start + descriptorToken.length) !==
|
||||
'volatile'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const nextTokenType = this._peekToken(1).type;
|
||||
return nextTokenType === TokenType.Identifier || nextTokenType === TokenType.OpenParenthesis;
|
||||
}
|
||||
|
||||
// Viper 扩展:消费值位置的 `volatile` 前缀,返回被修饰的调用节点。
|
||||
// 语义上等价于该调用本身(volatile 仅影响代码生成,不改变类型),故直接返回
|
||||
// CallNode:`volatile f()` 的类型即 f 的返回类型、`volatile 结构体()` 即结构体实例。
|
||||
// 非调用形态(如 `volatile x`)报语法错误但仍返回已解析的表达式以维持错误恢复。
|
||||
private _parseViperVolatileValue(): ExpressionNode {
|
||||
const volatileToken = this._getNextToken();
|
||||
const innerExpression = this._parseAtomExpression();
|
||||
if (innerExpression.nodeType !== ParseNodeType.Call) {
|
||||
this._addSyntaxError(LocMessage.viperVolatileValueRequiresCall(), volatileToken);
|
||||
}
|
||||
return innerExpression;
|
||||
}
|
||||
|
||||
// Viper 扩展:类型描述符前缀 `const T` / `volatile T`。
|
||||
// 解析层脱糖为等价的联合类型注解 `CConst | T` / `CVolatile | T`,
|
||||
// 与手写 `a: t.CConst | CInt` 的 AST 同形,从而完全复用既有 TypeSet
|
||||
// 修饰符语义(类型求值、赋值兼容、运算跳过修饰符、hover、语义高亮均一致),
|
||||
// 不引入第二套修饰机制。
|
||||
//
|
||||
// 硬关键字:const / volatile 是 Viper 保留字(tokenizer 生成
|
||||
// KeywordType.ViperTypeDescriptor),语法上只允许「类型注解前缀」这一种用法,
|
||||
// 其余位置(`const = 1`、`x.volatile`、注解中不跟类型等)由 parser 报语法错误。
|
||||
//
|
||||
// 脱糖目标 `CConst` / `CVolatile` 是 builtins stub 中的别名
|
||||
// (见 typeshed-fallback/stdlib/builtins.vpi),故不要求源码 import t。
|
||||
private _parseViperTypeDescriptorPrefix(): ExpressionNode | undefined {
|
||||
if (this._parseOptions.dialect !== 'viper') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const descriptorToken = this._peekToken();
|
||||
if (
|
||||
descriptorToken.type !== TokenType.Keyword ||
|
||||
(descriptorToken as KeywordToken).keywordType !== KeywordType.ViperTypeDescriptor
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const descriptorName = this._fileContents!.slice(
|
||||
descriptorToken.start,
|
||||
descriptorToken.start + descriptorToken.length
|
||||
);
|
||||
const memberName = descriptorName === 'const' ? 'CConst' : 'CVolatile';
|
||||
|
||||
// 硬关键字:紧随其后必须是类型原子的起点(标识符 / '[' / 另一个描述符 /
|
||||
// `__attribute__(` 属性前缀),否则报语法错误(如 `const = 1`、`a: const`)。
|
||||
const nextToken = this._peekToken(1);
|
||||
const isTypeAtomStart =
|
||||
this._isViperTypeAtomStartToken(nextToken) || this._isAtViperAttributePrefix(1);
|
||||
if (!isTypeAtomStart) {
|
||||
this._addSyntaxError(
|
||||
LocMessage.viperTypeDescriptorRequiresType().format({ name: descriptorName }),
|
||||
descriptorToken
|
||||
);
|
||||
this._getNextToken();
|
||||
return ErrorNode.create(descriptorToken, ErrorExpressionCategory.MissingExpression);
|
||||
}
|
||||
|
||||
// 消费描述符关键字。
|
||||
this._getNextToken();
|
||||
|
||||
// 递归解析描述符修饰的主体:支持 `const volatile int`、`const [4]int`。
|
||||
// 期间禁止在表达式层就地消费指针后缀 `*`(置 _isParsingViperTypeDescriptorInner),
|
||||
// 使 `const int*` 恒为 `(CConst | int)*` —— 指针后缀由 _parseTypeAnnotation
|
||||
// 统一在最外层应用(指针指向被描述符修饰的类型)。
|
||||
const wasDescriptorInner = this._isParsingViperTypeDescriptorInner;
|
||||
this._isParsingViperTypeDescriptorInner = true;
|
||||
const innerType = this._parseViperTypeAnnotationCore();
|
||||
this._isParsingViperTypeDescriptorInner = wasDescriptorInner;
|
||||
|
||||
// 合成描述符名:零长度、位置落在描述符关键字起点,不产生语义高亮
|
||||
// (semanticTokenProvider 跳过 length <= 0 的名字),也不参与位置命中。
|
||||
const modifierNameNode = NameNode.create(
|
||||
IdentifierToken.create(descriptorToken.start, 0, memberName, /* comments */ undefined)
|
||||
);
|
||||
modifierNameNode.d.isViperTypeDescriptor = true;
|
||||
|
||||
// 合成 '|' 算子 token:源码中不存在,位置紧随描述符关键字,零长度。
|
||||
const operatorToken = OperatorToken.create(
|
||||
descriptorToken.start + descriptorToken.length,
|
||||
0,
|
||||
OperatorType.BitwiseOr,
|
||||
/* comments */ undefined
|
||||
);
|
||||
return BinaryOperationNode.create(modifierNameNode, innerType, operatorToken, OperatorType.BitwiseOr);
|
||||
}
|
||||
|
||||
// Viper 扩展:判定当前 '*'/'**' 串是否为类型注解内的 C 指针后缀。
|
||||
// 判据:星号串('*' 一层 / '**' 两层,可含换行)之后紧跟"无右操作数"位置,
|
||||
// 即 ']' / ',' / ')' / ':' / '=' / 换行 / ';' / 文件结束。
|
||||
@@ -5187,6 +5663,56 @@ export class Parser {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Viper 扩展:判定当前 '*'/'**' 串是否为 `T*(x)` 形式的 C 风格指针强转调用。
|
||||
// 判据(三条同时成立,与 TPC ExprParser._AtPtrSuffixCall 同源):
|
||||
// 1. base 是「类型原子」形态(Name / MemberAccess / Index);
|
||||
// 2. base 与星串**紧贴**(源码字符相邻,无空白/换行);
|
||||
// 3. 星串内部逐个紧贴,且其**紧贴**一个 '('。
|
||||
// 为什么 '(' 不并入 _isAtViperPointerTypeSuffix 的「无右操作数位置」:`a * (b)` 是
|
||||
// 合法乘法('(' 可作乘法右操作数的起点)。故叠加「前驱是类型原子 + 两侧紧贴」两条,
|
||||
// 使 `a * (b + c)`、`f(x) * (y)`、`(i + 1) * (j)` 仍按乘法解析。
|
||||
private _isAtViperPointerSuffixCastCall(baseExpr: ExpressionNode): boolean {
|
||||
if (this._parseOptions.dialect !== 'viper') {
|
||||
return false;
|
||||
}
|
||||
switch (baseExpr.nodeType) {
|
||||
case ParseNodeType.Name:
|
||||
case ParseNodeType.MemberAccess:
|
||||
case ParseNodeType.Index:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// base 与星串是否紧贴:用已消费的最后一个 token 的结束位置做判据。
|
||||
const prevToken = this._peekToken(-1);
|
||||
let starEndOffset = prevToken.start + prevToken.length;
|
||||
let offset = 0;
|
||||
let starCount = 0;
|
||||
while (true) {
|
||||
const token = this._peekToken(offset);
|
||||
const operatorType =
|
||||
token.type === TokenType.Operator ? (token as OperatorToken).operatorType : undefined;
|
||||
if (operatorType !== OperatorType.Multiply && operatorType !== OperatorType.Power) {
|
||||
break;
|
||||
}
|
||||
if (token.start !== starEndOffset) {
|
||||
// base 与星串之间、或星串内部有空白:非 C 风格强转写法。
|
||||
return false;
|
||||
}
|
||||
starEndOffset = token.start + token.length;
|
||||
starCount += 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
if (starCount === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nextToken = this._peekToken(offset);
|
||||
return nextToken.type === TokenType.OpenParenthesis && nextToken.start === starEndOffset;
|
||||
}
|
||||
|
||||
// Viper 扩展:定长数组类型语法 `[SIZE]T`(等价 t.CArray[T, SIZE])与
|
||||
// 不定长写法 `[]T`(等价 t.CArray[T],与 `t.CArray[T]` 单参数一致)。
|
||||
// 生效位置:
|
||||
|
||||
@@ -646,6 +646,8 @@ enum MagicsKind {
|
||||
// Viper 扩展:C 风格修饰/声明关键字处理。
|
||||
// Viper 支持将 C 修饰/声明语义写成语句前缀关键字:
|
||||
// inline/export/extern def f(...) —— 修饰关键字,必须与 def 搭配
|
||||
// virtual/novirtual class Foo —— 类级 vtable 声明标记
|
||||
// virtual/novirtual def f(...) —— 方法级 vtable 声明标记(须在类内)
|
||||
// fn/fun/func/function f(...) —— def 的别名
|
||||
// enum/rnum class|struct Foo —— 跳过前缀关键字(后续 class 正常解析)
|
||||
// enum Foo: —— 等价 `class Foo(t.CEnum):`(枚举)
|
||||
@@ -656,8 +658,8 @@ enum MagicsKind {
|
||||
// 返回 'classEnum':把 enum 当作 class 关键字,并标记为 Viper 枚举
|
||||
// (等价 `class Foo(t.CEnum)`,analyzer 会赋予 CEnum 枚举语义);
|
||||
// 返回 'def':把 fn/fun/func/function 当作 def 关键字;
|
||||
// 返回 'viperModifier':inline/export/extern 生成修饰关键字 token,由 parser
|
||||
// 判断是否与 def 搭配(搭配则跳过并记录,单独使用则报错);
|
||||
// 返回 'viperModifier':inline/export/extern/virtual/novirtual 生成修饰关键字
|
||||
// token,由 parser 判断搭配是否合法(单独使用则报错);
|
||||
// undefined:按普通标识符处理(变量名、成员名、导入名、`state:` 注解等)。
|
||||
function getViperStatementKeywordAction(
|
||||
text: string,
|
||||
@@ -667,8 +669,14 @@ function getViperStatementKeywordAction(
|
||||
precededByViperModifier: boolean
|
||||
): 'skip' | 'class' | 'classEnum' | 'def' | 'viperModifier' | undefined {
|
||||
const nextWord = peekViperKeywordWord(text, identifierEnd);
|
||||
if (name === 'inline' || name === 'export' || name === 'extern') {
|
||||
// 修饰关键字仅在语句起始处生效,避免误伤 `self.inline` 等成员名。
|
||||
if (
|
||||
name === 'inline' ||
|
||||
name === 'export' ||
|
||||
name === 'extern' ||
|
||||
name === 'virtual' ||
|
||||
name === 'novirtual'
|
||||
) {
|
||||
// 修饰关键字仅在语句起始处生效,避免误伤 `self.inline` / `self.virtual` 等成员名。
|
||||
if (!isViperAtStatementStart(text, identifierStart)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -676,7 +684,7 @@ function getViperStatementKeywordAction(
|
||||
if (isViperNextCharColon(text, identifierEnd)) {
|
||||
return undefined;
|
||||
}
|
||||
// 一律生成修饰关键字 token,由 parser 判断是否与 def 搭配。
|
||||
// 一律生成修饰关键字 token,由 parser 判断搭配是否合法。
|
||||
return 'viperModifier';
|
||||
}
|
||||
if (name === 'fn' || name === 'fun' || name === 'func' || name === 'function') {
|
||||
@@ -1504,6 +1512,16 @@ export class Tokenizer {
|
||||
this._tokens.push(KeywordToken.create(start, length, keywordType, this._getComments()));
|
||||
} else {
|
||||
const value = this._internIdentifier(text, start, end, length);
|
||||
// Viper 扩展:类型描述符 const / volatile 是硬关键字(Viper 方言保留字),
|
||||
// 出现在任何位置都生成关键字 token,不再作为普通标识符 ——
|
||||
// 由此 `const = 1` / `x.volatile` 之类误用会在 parser 层直接报语法错误,
|
||||
// 而正确的用法只有「类型注解前缀」(见 parser._parseViperTypeDescriptorPrefix)。
|
||||
if (value === 'const' || value === 'volatile') {
|
||||
this._tokens.push(
|
||||
KeywordToken.create(start, length, KeywordType.ViperTypeDescriptor, this._getComments())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
// Viper 扩展:C 风格修饰/声明关键字。
|
||||
// inline/export/extern 生成修饰关键字 token(parser 判断是否配 def);
|
||||
// enum/rnum/struct 在语句起始且后跟类名时按 class 关键字处理;
|
||||
|
||||
@@ -154,6 +154,8 @@ export const enum KeywordType {
|
||||
Yield,
|
||||
// Viper 扩展:inline/export/extern 等 C 风格修饰关键字(必须与 def 搭配)。
|
||||
ViperModifier,
|
||||
// Viper 扩展:类型描述符 const / volatile(硬关键字,只能作为类型注解前缀)。
|
||||
ViperTypeDescriptor,
|
||||
}
|
||||
|
||||
export const softKeywords = [
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* viperAttribute.test.ts
|
||||
*
|
||||
* 验证 Viper 的 `__attribute__` 与值位置 `volatile` 前缀(仅 LSP 侧实现,
|
||||
* TPC/TPV 不处理):
|
||||
* ① `@__attribute__(section(".x"), always_inline)` 装饰器
|
||||
* → 脱糖为 `@c.Attribute(t.attr.section(".x"), t.attr.always_inline)`(class/def 通用)
|
||||
* ② `a: __attribute__(section(".x")) const int = 1` 注解修饰器
|
||||
* → 脱糖为 `(t.attr.section(".x") | (CConst | int))`,与 const/volatile 同族
|
||||
* ③ 注解联合里的属性标记 `T | t.attr.<name>` / `T | t.attr.llvm.<name>`
|
||||
* 求值为 t.CAttr(修饰符),不参与类型检查、展示不泄漏
|
||||
* ④ 值位置 `volatile f()` / `volatile 结构体()`(标记调用为 volatile)
|
||||
* ⑤ `__attribute__(` 括号内的属性名补全
|
||||
* 负例:`volatile = 2` / `const = 1` / `volatile 1`(非调用)仍须报错。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperAttribute.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { CancellationToken, MarkupKind, Position } from 'vscode-languageserver';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
|
||||
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';
|
||||
import { CompletionProvider } from '../languageService/completionProvider';
|
||||
import { HoverProvider } from '../languageService/hoverProvider';
|
||||
import { ParseNodeType, TypeAnnotationNode } from '../parser/parseNodes';
|
||||
|
||||
(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, 'attribute.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const parseResults = program.getParseResults(uri);
|
||||
assert(parseResults, 'parse results not found');
|
||||
|
||||
return { program, configOptions, uri, serviceProvider, testDir, parseResults };
|
||||
}
|
||||
|
||||
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): { category: number; line: number; message: string }[] {
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
/** 收集注解变量的展示类型(按变量名索引)。 */
|
||||
function collectAnnotatedVarTypes(program: Program, parseResults: any): Map<string, string> {
|
||||
const evaluator = program.evaluator;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const result = new Map<string, string>();
|
||||
|
||||
class Collector extends ParseTreeWalker {
|
||||
override visitTypeAnnotation(node: TypeAnnotationNode) {
|
||||
const valueExpr: any = node.d.valueExpr;
|
||||
if (valueExpr.nodeType === ParseNodeType.Name) {
|
||||
const type = evaluator!.getType(valueExpr);
|
||||
// 取第一个(顶层声明),避免函数内局部同名覆盖
|
||||
if (!result.has(valueExpr.d.value)) {
|
||||
result.set(valueExpr.d.value, type ? program.printType(type) : '(undefined)');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new Collector().walk(parseResults.parserOutput.parseTree);
|
||||
return result;
|
||||
}
|
||||
|
||||
test('__attribute__ 装饰器 / 修饰器 / 注解联合标记(LSP 侧)', () => {
|
||||
const code = `import t, c
|
||||
|
||||
@__attribute__(section(".text.wo"), always_inline)
|
||||
def decorated(a: int) -> int:
|
||||
return a
|
||||
|
||||
@__attribute__(packed, aligned(16))
|
||||
class Packed:
|
||||
x: t.CInt
|
||||
|
||||
AttrConst: __attribute__(section(".PRVERSN")) const int = 1
|
||||
|
||||
P: t.CInt | t.attr.llvm.nounwind | t.attr.llvm.nobuiltin = 0
|
||||
|
||||
def use_p(p: P) -> int:
|
||||
return p + 1
|
||||
|
||||
V: str | t.attr.weak = "x"
|
||||
|
||||
def use_v(v: V) -> int:
|
||||
return v[0]
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testDir, parseResults } = buildProgramForCode(
|
||||
code,
|
||||
'__viper_attribute__'
|
||||
);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== __attribute__ 诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
// ① `__attribute__` / `section` / `always_inline` 等不应报「未定义」
|
||||
const undefinedDiags = diagnostics.filter((d) => d.message.includes('未定义') || d.message.includes('undefined'));
|
||||
assert.strictEqual(
|
||||
undefinedDiags.length,
|
||||
0,
|
||||
`__attribute__ 及其属性名不应报未定义:${JSON.stringify(undefinedDiags)}`
|
||||
);
|
||||
|
||||
// ② 属性标记不得触发运算/下标/类型注解调用等诊断
|
||||
const forbidden = diagnostics.filter(
|
||||
(d) =>
|
||||
d.message.includes('运算符') ||
|
||||
d.message.includes('下标') ||
|
||||
d.message.includes('调用表达式')
|
||||
);
|
||||
assert.strictEqual(forbidden.length, 0, `属性标记应被当作修饰符跳过:${JSON.stringify(forbidden)}`);
|
||||
|
||||
// ③ 展示不泄漏 CAttr / nounwind / nobuiltin / weak
|
||||
const types = collectAnnotatedVarTypes(program, parseResults);
|
||||
console.log('=== 注解变量类型 ===');
|
||||
types.forEach((v, k) => console.log(` ${k}: ${v}`));
|
||||
|
||||
assert(
|
||||
types.get('P')!.includes('CInt') && !types.get('P')!.includes('CAttr'),
|
||||
`P 应展示为基础类型(属性标记被丢弃),实际: ${types.get('P')}`
|
||||
);
|
||||
assert(
|
||||
!types.get('P')!.includes('nounwind') && !types.get('P')!.includes('nobuiltin'),
|
||||
`P 不应泄漏 llvm 属性标记,实际: ${types.get('P')}`
|
||||
);
|
||||
assert(
|
||||
types.get('AttrConst')!.includes('const') && !types.get('AttrConst')!.includes('CAttr'),
|
||||
`AttrConst 应展示为 const int,实际: ${types.get('AttrConst')}`
|
||||
);
|
||||
assert(
|
||||
!types.get('V')!.includes('CAttr') && !types.get('V')!.includes('weak'),
|
||||
`V 不应泄漏属性标记,实际: ${types.get('V')}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('值位置 volatile 前缀:volatile f() / volatile 结构体()', () => {
|
||||
const code = `import t
|
||||
|
||||
class Settings:
|
||||
x: int
|
||||
|
||||
def f() -> int:
|
||||
return 1
|
||||
|
||||
def use() -> int:
|
||||
a: int = volatile f()
|
||||
b: Settings = volatile Settings()
|
||||
return a
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_volatile__');
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== volatile 值前缀诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
// 只关心 error/warning;category=3 是「未存取」类 hint(未使用的 import/变量)
|
||||
const realDiagnostics = diagnostics.filter((d) => d.category === 0 || d.category === 1);
|
||||
assert.strictEqual(
|
||||
realDiagnostics.length,
|
||||
0,
|
||||
`volatile 值前缀不应产生错误/警告:${JSON.stringify(realDiagnostics)}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('volatile / const 裸用与非调用前缀仍报错', () => {
|
||||
const code = `const = 1
|
||||
volatile = 2
|
||||
|
||||
def use() -> int:
|
||||
x = volatile 1
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_volatile_bad__');
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== volatile/const 裸用诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const syntaxErrors = diagnostics.filter((d) => d.category === 0);
|
||||
assert(
|
||||
syntaxErrors.length >= 2,
|
||||
`const/volatile 裸用与非调用前缀应报语法错误:${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('__attribute__(...) 括号内提示属性名', async () => {
|
||||
// 光标落在 `__attribute__(` 之后(第 2 行第 20 列,即 '(' 与 ')' 之间)。
|
||||
const code = `import t
|
||||
|
||||
Attr: __attribute__() int = 1
|
||||
`;
|
||||
const { program, uri, serviceProvider, testDir } = buildProgramForCode(
|
||||
code,
|
||||
'__viper_attribute_completion__'
|
||||
);
|
||||
|
||||
try {
|
||||
const provider = new CompletionProvider(
|
||||
program as any,
|
||||
uri,
|
||||
Position.create(2, 20),
|
||||
{ format: MarkupKind.PlainText, snippet: false, lazyEdit: false },
|
||||
CancellationToken.None
|
||||
);
|
||||
const completions = await provider.getCompletions();
|
||||
assert(completions, '补全结果为空');
|
||||
|
||||
const labels: string[] = completions.items.map((item) => item.label);
|
||||
console.log(`=== __attribute__ 补全项 ${labels.length} 项:${labels.slice(0, 20).join(', ')} ===`);
|
||||
|
||||
for (const name of ['section', 'always_inline', 'aligned', 'packed', 'weak', 'noinline']) {
|
||||
assert(labels.indexOf(name) >= 0, `__attribute__ 补全缺少属性 ${name}:${JSON.stringify(labels)}`);
|
||||
}
|
||||
// llvm 描述符类不是 __attribute__ 的属性名(属注解联合用法),不应出现在此处
|
||||
assert(labels.indexOf('nounwind') < 0, `llvm 描述符类不应出现在 __attribute__ 补全里:${JSON.stringify(labels)}`);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('键入 __a 时补全里能看到 __attribute__', async () => {
|
||||
// 装饰器位置(第 2 行第 4 列,`@__a` 之后)与注解位置(第 4 行第 10 列,`x: __a` 之后)。
|
||||
const code = `import t
|
||||
|
||||
@__a
|
||||
def f() -> int:
|
||||
x: __a = 1
|
||||
return 0
|
||||
`;
|
||||
const { program, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_attribute_prefix__');
|
||||
|
||||
try {
|
||||
for (const position of [Position.create(2, 4), Position.create(4, 10)]) {
|
||||
const provider = new CompletionProvider(
|
||||
program as any,
|
||||
uri,
|
||||
position,
|
||||
{ format: MarkupKind.PlainText, snippet: false, lazyEdit: false },
|
||||
CancellationToken.None
|
||||
);
|
||||
const completions = await provider.getCompletions();
|
||||
assert(completions, '补全结果为空');
|
||||
|
||||
const labels: string[] = completions.items.map((item) => item.label);
|
||||
assert(
|
||||
labels.indexOf('__attribute__') >= 0,
|
||||
`行 ${position.line} 键入 __a 应提示 __attribute__:${JSON.stringify(labels)}`
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('hover 在 __attribute__(...) 括号内提示全部属性名', () => {
|
||||
// 第 2 行第 22 列落在实参 `section` 上。
|
||||
const code = `import t
|
||||
|
||||
Attr: __attribute__(section(".x")) const int = 1
|
||||
`;
|
||||
const { program, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_attribute_hover__');
|
||||
|
||||
try {
|
||||
const provider = new HoverProvider(program as any, uri, Position.create(2, 22), MarkupKind.Markdown, CancellationToken.None);
|
||||
const hover = provider.getHover();
|
||||
assert(hover, 'hover 结果为空');
|
||||
|
||||
const text = typeof hover.contents === 'string' ? hover.contents : (hover.contents as any).value;
|
||||
console.log(`=== hover 内容(前 200 字)===\n${text.slice(0, 200)}`);
|
||||
|
||||
for (const name of ['section', 'always_inline', 'aligned', 'packed', 'weak']) {
|
||||
assert(text.includes(name), `hover 应列出属性 ${name}:${text.slice(0, 300)}`);
|
||||
}
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -442,7 +442,7 @@ def f() -> int:
|
||||
// 回归:完全不依赖工程 includes/builtins.vp —— C 定宽/最快/最小整型 typedef 已内建到
|
||||
// typeshed-fallback/stdlib/builtins.vpi。用于修"uint32 报未定义"在找回 builtins.vp
|
||||
// 失败的工程(如 Trae 中打开的文件集合/工作区根不同)里依然复现的问题。
|
||||
test('无 includes/builtins.vp 时,内建 builtins.vpi 提供 uint32/sizet/int8', () => {
|
||||
test('无 includes/builtins.vp 时,内建 builtins.vpi 提供 uint32/sizet/int8/uint/uchar', () => {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(
|
||||
createFromRealFileSystem(tempFile),
|
||||
@@ -462,7 +462,7 @@ test('无 includes/builtins.vp 时,内建 builtins.vpi 提供 uint32/sizet/int
|
||||
def f(a: uint32, b: sizet, c: int8, d: uint64) -> uint32:
|
||||
return a
|
||||
|
||||
def g(e: long, h: longlong, i: short, j: ushort, k: ulong, l: ulonglong) -> long:
|
||||
def g(e: long, h: longlong, i: short, j: ushort, k: ulong, l: ulonglong, m: uint, n: uchar) -> long:
|
||||
return e
|
||||
`,
|
||||
'utf8'
|
||||
@@ -515,9 +515,19 @@ def g(e: long, h: longlong, i: short, j: ushort, k: ulong, l: ulonglong) -> long
|
||||
);
|
||||
}
|
||||
|
||||
// C 基本类型名:long → CLong,longlong → CLongLong,short/ushort/ulong/ulonglong
|
||||
// C 基本类型名:long → CLong,longlong → CLongLong,short/ushort/ulong/ulonglong,
|
||||
// uint → CUnsignedInt,uchar → CUnsignedChar
|
||||
const gTypes = types.get('g') ?? [];
|
||||
for (const expected of ['CLong', 'CLongLong', 'CShort', 'CUnsignedShort', 'CUnsignedLong', 'CUnsignedLongLong']) {
|
||||
for (const expected of [
|
||||
'CLong',
|
||||
'CLongLong',
|
||||
'CShort',
|
||||
'CUnsignedShort',
|
||||
'CUnsignedLong',
|
||||
'CUnsignedLongLong',
|
||||
'CUnsignedInt',
|
||||
'CUnsignedChar',
|
||||
]) {
|
||||
assert(
|
||||
gTypes.some((t) => t.includes(expected)),
|
||||
`g() 参数注解应包含 ${expected},实际: ${JSON.stringify(gTypes)}`
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* viperCallable.test.ts
|
||||
*
|
||||
* 验证 Viper 的注解位置小写 `callable[...]` 等价 `t.Callable`:
|
||||
* callable[[A, B], R] / callable[..., R] / callable[[], R] ≡ t.Callable[...]
|
||||
* 实现:parser 在**类型注解位置**把裸名 `callable` 脱糖为 `Callable`
|
||||
* (parser._parseAtom,仅 `_isParsingTypeAnnotation` 为真时),
|
||||
* `Callable` 在 builtins stub 中以 `Callable: t.CTypedef = t.Callable` 声明,
|
||||
* 故不要求源码 `import t`。**值位置不受影响**:`callable(x)` 仍是内置函数。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperCallable.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
|
||||
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';
|
||||
import { FunctionNode, ParseNodeType, TypeAnnotationNode } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgramForCode(code: 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, '__viper_callable__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'callable.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const parseResults = program.getParseResults(uri);
|
||||
assert(parseResults, 'parse results not found');
|
||||
|
||||
return { program, configOptions, uri, serviceProvider, testDir, parseResults };
|
||||
}
|
||||
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
/** 收集「模块级注解变量 + 函数参数注解 + 函数返回注解」的展示类型。 */
|
||||
function collectAnnotationTypes(program: Program, parseResults: any): Map<string, string> {
|
||||
const evaluator = program.evaluator;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const result = new Map<string, string>();
|
||||
|
||||
const record = (key: string, node: any) => {
|
||||
if (!node || result.has(key)) {
|
||||
return;
|
||||
}
|
||||
const type = evaluator!.getType(node);
|
||||
result.set(key, type ? program.printType(type) : '(undefined)');
|
||||
};
|
||||
|
||||
class Collector extends ParseTreeWalker {
|
||||
override visitTypeAnnotation(node: TypeAnnotationNode) {
|
||||
if (node.d.valueExpr.nodeType === ParseNodeType.Name) {
|
||||
record(node.d.valueExpr.d.value, node.d.annotation);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
override visitFunction(node: FunctionNode) {
|
||||
node.d.params.forEach((param) => {
|
||||
if (param.d.name && param.d.annotation) {
|
||||
record(param.d.name.d.value, param.d.annotation);
|
||||
}
|
||||
});
|
||||
record(`${node.d.name.d.value}()`, node.d.returnAnnotation);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new Collector().walk(parseResults.parserOutput.parseTree);
|
||||
return result;
|
||||
}
|
||||
|
||||
test('注解位置 callable[[A, B], R] 等价 t.Callable / Callable(无需 import t)', () => {
|
||||
const code = `A: callable[[int, str], bool] = None
|
||||
B: Callable[[int], bool] = None
|
||||
Dotsys: callable[..., bool] = None
|
||||
Empty: callable[[], bool] = None
|
||||
|
||||
def f(cb: callable[[int, str], bool], cb2: callable[..., bool], cb3: callable[[], bool]) -> None:
|
||||
pass
|
||||
|
||||
def g() -> callable[[int], bool]:
|
||||
return None
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testDir, parseResults } = buildProgramForCode(code);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== callable 注解诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const realDiagnostics = diagnostics.filter((d) => d.category === 0 || d.category === 1);
|
||||
assert.strictEqual(
|
||||
realDiagnostics.length,
|
||||
0,
|
||||
`callable[...] 注解不应产生错误/警告:${JSON.stringify(realDiagnostics)}`
|
||||
);
|
||||
|
||||
const types = collectAnnotationTypes(program, parseResults);
|
||||
console.log('=== callable 注解类型 ===');
|
||||
types.forEach((v, k) => console.log(` ${k}: ${v}`));
|
||||
|
||||
// 注解位置的求值结果是「可实例化」形式(type[...]),与 t.Callable[...] 完全一致
|
||||
assert.strictEqual(
|
||||
types.get('A'),
|
||||
'type[(int, str) -> bool]',
|
||||
`A 应为 type[(int, str) -> bool],实际: ${types.get('A')}`
|
||||
);
|
||||
assert.strictEqual(types.get('B'), 'type[(int) -> bool]', `B 应为 type[(int) -> bool],实际: ${types.get('B')}`);
|
||||
assert.strictEqual(
|
||||
types.get('Dotsys'),
|
||||
'type[(...) -> bool]',
|
||||
`Dotsys 应为 type[(...) -> bool],实际: ${types.get('Dotsys')}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
types.get('Empty'),
|
||||
'type[() -> bool]',
|
||||
`Empty 应为 type[() -> bool],实际: ${types.get('Empty')}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
types.get('cb'),
|
||||
'type[(int, str) -> bool]',
|
||||
`cb 应为 type[(int, str) -> bool],实际: ${types.get('cb')}`
|
||||
);
|
||||
assert.strictEqual(types.get('cb2'), 'type[(...) -> bool]', `cb2 应为 type[(...) -> bool],实际: ${types.get('cb2')}`);
|
||||
assert.strictEqual(types.get('cb3'), 'type[() -> bool]', `cb3 应为 type[() -> bool],实际: ${types.get('cb3')}`);
|
||||
assert.strictEqual(
|
||||
types.get('g()'),
|
||||
'type[(int) -> bool]',
|
||||
`g 的返回注解应为 type[(int) -> bool],实际: ${types.get('g()')}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('值位置 callable(x) 仍是内置函数(不脱糖)', () => {
|
||||
const code = `def value_pos(x: object) -> None:
|
||||
if callable(x):
|
||||
pass
|
||||
|
||||
def value_pos2(x: object) -> bool:
|
||||
y: bool = callable(x)
|
||||
return y
|
||||
|
||||
def value_subscript(x: object) -> None:
|
||||
z = callable[[int], bool]
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testDir, parseResults } = buildProgramForCode(code);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== callable 值位置诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const realDiagnostics = diagnostics.filter((d) => d.category === 0 || d.category === 1);
|
||||
assert.strictEqual(
|
||||
realDiagnostics.length,
|
||||
0,
|
||||
`值位置 callable(x) 不应产生错误/警告:${JSON.stringify(realDiagnostics)}`
|
||||
);
|
||||
|
||||
// 值位置的 `callable[[int], bool]` 不得变成可调用类型(说明未在值位置脱糖)
|
||||
const types = collectAnnotationTypes(program, parseResults);
|
||||
console.log('=== 值位置注解类型 ===');
|
||||
types.forEach((v, k) => console.log(` ${k}: ${v}`));
|
||||
|
||||
const xType = types.get('x') ?? '';
|
||||
assert(
|
||||
!xType.includes('->'),
|
||||
`值位置的 callable 不应被当作类型脱糖(x 的注解仍应为 object),实际: ${xType}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* viperCompletionKeyword.test.ts
|
||||
*
|
||||
* 验证 Viper 方言专有关键字出现在补全提示里:硬关键字 define / const / volatile,
|
||||
* 软关键字 typedef / inline / export / extern / virtual / novirtual / enum / rnum /
|
||||
* union / struct,以及 def 别名 fn / fun / func / function。
|
||||
* 这些词不在 CPython 关键字表里,若未并入 Keywords 列表则补全时"看不见"。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperCompletionKeyword.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { CancellationToken, CompletionItemKind, MarkupKind, Position } from 'vscode-languageserver';
|
||||
|
||||
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';
|
||||
import { CompletionProvider } from '../languageService/completionProvider';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
/** 期望出现在补全里的 Viper 关键字(硬关键字 + 软关键字 + def 别名)。 */
|
||||
const ViperKeywords: string[] = [
|
||||
'define',
|
||||
'const',
|
||||
'volatile',
|
||||
'typedef',
|
||||
'inline',
|
||||
'export',
|
||||
'extern',
|
||||
'virtual',
|
||||
'novirtual',
|
||||
'enum',
|
||||
'rnum',
|
||||
'union',
|
||||
'struct',
|
||||
'fn',
|
||||
'fun',
|
||||
'func',
|
||||
'function',
|
||||
];
|
||||
|
||||
function buildProgramForCode(code: 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, '__viper_completion_keyword__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'completion_keyword.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
return { program, configOptions, uri, serviceProvider, testDir };
|
||||
}
|
||||
|
||||
/** 与内置符号同名的关键字:补全里会先命中该符号(如 `class function` 来自
|
||||
* builtins stub),按符号种类提示而非 Keyword,故只校验"能被提示到"。 */
|
||||
const SymbolCollidingKeywords = new Set(['function']);
|
||||
|
||||
test('Viper 补全提示包含 Viper 专有关键字', async () => {
|
||||
// 第 2 行(索引 1)是函数体内空语句槽;补全位置取该行缩进之后。
|
||||
const code = `def f() -> int:
|
||||
|
||||
return 0
|
||||
`;
|
||||
const { program, uri, serviceProvider, testDir } = buildProgramForCode(code);
|
||||
|
||||
try {
|
||||
const provider = new CompletionProvider(
|
||||
program,
|
||||
uri,
|
||||
Position.create(1, 4),
|
||||
{ format: MarkupKind.PlainText, snippet: false, lazyEdit: false },
|
||||
CancellationToken.None
|
||||
);
|
||||
const completions = await provider.getCompletions();
|
||||
assert(completions, '补全结果为空');
|
||||
|
||||
const labels: string[] = completions.items.map((item) => item.label);
|
||||
console.log(
|
||||
`=== 补全项 ${labels.length} 项,Viper 关键字命中 ${
|
||||
ViperKeywords.filter((keyword) => labels.indexOf(keyword) >= 0).length
|
||||
}/${ViperKeywords.length} ===`
|
||||
);
|
||||
|
||||
for (const keyword of ViperKeywords) {
|
||||
const index = labels.indexOf(keyword);
|
||||
assert(index >= 0, `补全里缺少 Viper 关键字 ${keyword}:${JSON.stringify(labels)}`);
|
||||
if (!SymbolCollidingKeywords.has(keyword)) {
|
||||
assert.strictEqual(
|
||||
completions.items[index].kind,
|
||||
CompletionItemKind.Keyword,
|
||||
`${keyword} 的 kind 应为 Keyword`
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -722,7 +722,93 @@ def test_builtins_asm_llvmir():
|
||||
});
|
||||
|
||||
|
||||
test('Viper len 接受任意值并返回 t.SizeT', () => {
|
||||
// Viper 扩展:C 风格内置 asm / LLVM IR 助记符 —— `__asm__` 等价 c.Asm,
|
||||
// `asminp`/`asmout` 等价 c.AsmInp/c.AsmOut,`__llvmir__` 等价 c.LLVMIR,
|
||||
// `irinp`/`irout` 等价 c.LInp/c.LOut,`ASM_DESCR` 免 `t.` 前缀;它们都是隐式
|
||||
// builtins,源码无需 `import c` / `import t` 即可直接使用(与 rawptr/uint32 同机制)。
|
||||
test('Viper 内置 __asm__ / asminp / asmout / __llvmir__ / irinp / irout / ASM_DESCR(免 c./t. 前缀)', () => {
|
||||
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];
|
||||
|
||||
// 刻意不写任何 import:新写法全靠隐式 builtins(与用户给的 outb 示例一致)。
|
||||
const code = `def outb(port: uint16, value: uint8):
|
||||
__asm__(f"""mov edx, {asminp(uint32(port), ASM_DESCR.REG_ANY)}
|
||||
|
||||
mov eax, {asminp(uint32(value), ASM_DESCR.REG_ANY)}
|
||||
|
||||
out dx, al""",
|
||||
op = [ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RAX])
|
||||
|
||||
def inb(port: uint16) -> uint8:
|
||||
r: uint8 = 0
|
||||
__asm__("in al, dx", out=[asmout(r, ASM_DESCR.OUTPUT_REG)], op=[ASM_DESCR.CLOBBER_RAX])
|
||||
return r
|
||||
|
||||
def with_inp(port: uint16):
|
||||
__asm__(f"mov edx, {asminp(port, ASM_DESCR.REG_ANY)}", inp=[asminp(port, ASM_DESCR.REG_ANY)])
|
||||
|
||||
def ir_ops(a: int, b: int) -> int:
|
||||
# __llvmir__:内联 IR 模板串 + 结果类型(第二个位置参数)
|
||||
r: int = __llvmir__(f"add i32 {irinp(a)}, {irinp(b)}", int)
|
||||
# 输出操作数 + 破坏列表
|
||||
__llvmir__(f"{irout(a)} = add i32 {irinp(a)}, {irinp(b)}", int,
|
||||
out=[irout(a)], op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
return r
|
||||
`;
|
||||
const testDir = path.join(__dirname, '__viper_asm_intrinsics__');
|
||||
const fs = require('fs');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'asm_intrinsics.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
try {
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== asm_intrinsics.vp diagnostics ===');
|
||||
diagnostics.forEach((d) => console.log(` ${d}`));
|
||||
|
||||
const errors = diagnostics.filter(
|
||||
(d) =>
|
||||
d.startsWith('[category=0]') ||
|
||||
(d.startsWith('[category=1]') && (d.includes('未定义') || d.includes('无法解析')))
|
||||
);
|
||||
assert.strictEqual(
|
||||
errors.length,
|
||||
0,
|
||||
`__asm__/asminp/asmout/ASM_DESCR 不应报错或未定义:${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
} finally {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
|
||||
test('Viper len 接受任意值并返回 t.SizeT', () => {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* viperGlobalRedeclare.test.ts
|
||||
*
|
||||
* 验证 Viper 的「重复声明」规则(与 TPV AssignGenMixin 同口径):
|
||||
* 模块级已有同名**运行期变量**时,函数内用**类型注解**声明同名变量无条件失败
|
||||
* (TypeError,判据不含类型是否一致):
|
||||
* ① 无 global/nonlocal → 会把全局遮蔽成局部 → 重复声明
|
||||
* ② 有 global/nonlocal → 注解与既有全局声明重复 → 重复声明
|
||||
* 合法写法:函数内 `global x` + 无注解赋值 `x = v`;或换不重名的局部名。
|
||||
* 不检查:模块级自身的重复注解、类体成员、CDefine 宏常量、CTypedef 别名、同名函数。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperGlobalRedeclare.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';
|
||||
|
||||
// 允许 vipershed-fallback(t.vpi/c.vpi 存根)被定位。
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgramForCode(code: 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, '__global_redeclare__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'global_redeclare.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
assert(sourceFile, 'source file not found');
|
||||
|
||||
return { program, configOptions, uri, serviceProvider, sourceFile };
|
||||
}
|
||||
|
||||
function getDiagnostics(sourceFile: any, configOptions: ConfigOptions): { category: number; line: number; message: string }[] {
|
||||
return (sourceFile.getDiagnostics(configOptions) || []).map((diag: any) => ({
|
||||
category: diag.category,
|
||||
line: diag.range ? diag.range.start.line + 1 : -1,
|
||||
message: diag.message,
|
||||
}));
|
||||
}
|
||||
|
||||
test('Viper 重复声明:函数内同名注解声明无条件失败(有/无 global 均报)', () => {
|
||||
const code = `import t
|
||||
|
||||
Counter: int = 0
|
||||
Macro: t.CDefine = 16
|
||||
Alias: t.CTypedef = uint32
|
||||
|
||||
def helper() -> int:
|
||||
return 0
|
||||
|
||||
def without_global() -> int:
|
||||
Counter: int = 1
|
||||
return 0
|
||||
|
||||
def with_global() -> int:
|
||||
global Counter
|
||||
Counter: int = 1
|
||||
return 0
|
||||
|
||||
def bare_annotation() -> int:
|
||||
Counter: int
|
||||
return 0
|
||||
|
||||
def ok_plain_assign() -> int:
|
||||
global Counter
|
||||
Counter = 1
|
||||
return 0
|
||||
|
||||
def ok_local() -> int:
|
||||
Other: int = 1
|
||||
return 0
|
||||
|
||||
def ok_macro() -> int:
|
||||
Macro: int = 1
|
||||
return 0
|
||||
|
||||
def ok_alias() -> int:
|
||||
Alias: int = 1
|
||||
return 0
|
||||
|
||||
def ok_func_name() -> int:
|
||||
helper: int = 1
|
||||
return 0
|
||||
|
||||
def ok_read() -> int:
|
||||
return Counter
|
||||
|
||||
class Holder:
|
||||
Counter: int = 2
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, sourceFile } = buildProgramForCode(code);
|
||||
|
||||
const diagnostics = getDiagnostics(sourceFile, configOptions);
|
||||
console.log('=== 重复声明诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
// 三处重复声明:L11(无 global)、L16(有 global)、L20(裸注解)
|
||||
const redeclarations = diagnostics.filter((d) => d.message.includes('Counter') && d.message.includes('global'));
|
||||
assert.strictEqual(
|
||||
redeclarations.length,
|
||||
3,
|
||||
`应报 3 处重复声明(L11/L16/L20): ${JSON.stringify(redeclarations)}`
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
redeclarations.map((d) => d.line).sort((a, b) => a - b),
|
||||
[11, 16, 20],
|
||||
`重复声明应落在 L11/L16/L20: ${JSON.stringify(redeclarations)}`
|
||||
);
|
||||
for (const diag of redeclarations) {
|
||||
assert.strictEqual(diag.category, 0, `重复声明应为 error(category=0): ${JSON.stringify(diag)}`);
|
||||
}
|
||||
|
||||
// 合法的两种写法与例外(CDefine 宏、CTypedef 别名、同名函数、类体成员、仅读取)不得报错
|
||||
const unexpectedErrors = diagnostics.filter((d) => d.category === 0 || d.category === 1);
|
||||
assert.strictEqual(
|
||||
unexpectedErrors.length,
|
||||
redeclarations.length,
|
||||
`除 3 处重复声明外不应有其它错误/警告: ${JSON.stringify(unexpectedErrors)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* viperInlineLoop.test.ts
|
||||
*
|
||||
* 验证 Viper 的 `inline while cond:` / `inline for x in y:` 写法(内联循环标记)。
|
||||
*
|
||||
* 语义取舍:inline 只是代码生成期的内联提示,类型层无额外语义;
|
||||
* 解析器消费修饰关键字后按普通 while / for 语句解析,不报语法错误。
|
||||
* 语法高亮由 TextMate grammar(storage.modifier.viper)负责,不涉及语义 token。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperInlineLoop.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
|
||||
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';
|
||||
import { convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { ForNode, WhileNode } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgram(source: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__inline_loop__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
fs.mkdirSync(testRoot, { recursive: true });
|
||||
const filePath = path.join(testRoot, 'main.vp');
|
||||
fs.writeFileSync(filePath, source, 'utf8');
|
||||
|
||||
const projectRoot = UriEx.file(testRoot);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
configOptions.executionEnvironments = [
|
||||
new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[UriEx.file(TRANSPYC_INCLUDES)],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
),
|
||||
];
|
||||
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
const uri = UriEx.file(filePath);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const parseResults = program.getSourceFile(uri)!.getParseResults();
|
||||
return { program, configOptions, parseResults, uri, serviceProvider };
|
||||
}
|
||||
|
||||
test('inline while / inline for 可解析且不报语法错误', () => {
|
||||
const source = `def f() -> int:
|
||||
i: int = 0
|
||||
inline while i < 3:
|
||||
i += 1
|
||||
inline for j in range(3):
|
||||
i += j
|
||||
return i
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
// 诊断:不得出现 Error / Warning 类别
|
||||
const diagnostics = (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
|
||||
(d) => `[category=${d.category}] ${d.message}`
|
||||
);
|
||||
const errors = diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
|
||||
assert.strictEqual(errors.length, 0, `inline 循环不应产生错误/警告,实际: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
// 形状:inline while → WhileNode;inline for → ForNode(修饰关键字被消费,不残留为表达式)
|
||||
const statements: { line: number; kind: 'while' | 'for' }[] = [];
|
||||
class LoopCollector extends ParseTreeWalker {
|
||||
override visitWhile(node: WhileNode) {
|
||||
statements.push({
|
||||
line: convertOffsetToPosition(node.start, parseResults!.parserOutput.lines).line + 1,
|
||||
kind: 'while',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
override visitFor(node: ForNode) {
|
||||
statements.push({
|
||||
line: convertOffsetToPosition(node.start, parseResults!.parserOutput.lines).line + 1,
|
||||
kind: 'for',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new LoopCollector().walk(parseResults!.parserOutput.parseTree);
|
||||
|
||||
// 行号指向 while / for 关键字自身(inline 在上一列),L3 / L5
|
||||
const whileNode = statements.find((s) => s.kind === 'while');
|
||||
assert(whileNode, `inline while 未解析为 WhileNode,实际: ${JSON.stringify(statements)}`);
|
||||
const forNode = statements.find((s) => s.kind === 'for');
|
||||
assert(forNode, `inline for 未解析为 ForNode,实际: ${JSON.stringify(statements)}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__inline_loop__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('inline 用于 while / for 之外仍报「必须与 def 搭配使用」', () => {
|
||||
const source = `def f() -> int:
|
||||
i: int = 0
|
||||
inline if i:
|
||||
pass
|
||||
return i
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const diagnostics = (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
|
||||
(d) => `[category=${d.category}] ${d.message}`
|
||||
);
|
||||
assert(
|
||||
diagnostics.some((d) => d.includes('inline') && d.includes('def')),
|
||||
`inline if 应仍报修饰关键字错误,实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__inline_loop__'), { recursive: true, force: true });
|
||||
});
|
||||
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* viperTypeDescriptor.test.ts
|
||||
*
|
||||
* 验证 Viper 类型描述符 `const T` / `volatile T`(`a: const int` / `a: volatile int`)。
|
||||
*
|
||||
* 实现取舍:const / volatile 是**硬关键字**(tokenizer 生成 ViperTypeDescriptor),
|
||||
* 语法上只允许「类型注解前缀」一种用法,其余位置报语法错误。解析层脱糖为等价的
|
||||
* 联合类型注解 `CConst | T` / `CVolatile | T`,完全复用既有 TypeSet 修饰符语义:
|
||||
* - 注解保留修饰符(CConst / CVolatile),无诊断
|
||||
* - `const T*` ≡ `(CConst | T)*` → `CConst | CPtr[T]`(指针后缀绑定到修饰后的类型,
|
||||
* 不因 `*` 之后是行尾还是下一行内容而在两条解析路径间摇摆)
|
||||
* - 脱糖目标 CConst / CVolatile 是 builtins stub 别名,不要求源码 import t
|
||||
* - 硬关键字边界:`const = 1`、`a: const`(不跟类型)报语法错误
|
||||
*
|
||||
* 运行:npx jest src/tests/viperTypeDescriptor.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { EvalFlags, TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
|
||||
import { isClass, isUnion } from '../analyzer/types';
|
||||
import { convertToInstance } from '../analyzer/typeUtils';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FunctionNode, ParseNodeType } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
// 按「注解求值」的方式求类型(EvalFlags.TypeExpression + convertToInstance),
|
||||
// 与 typeEvaluator.getTypeOfAnnotation 的处理一致,避免把 `X | Y` 当成值表达式
|
||||
// (此时 printType 只会打印 specialForm 名 "UnionType")。
|
||||
function evaluateAnnotation(evaluator: TypeEvaluator, annotation: any): any {
|
||||
const raw = evaluator.getTypeOfExpression(annotation, EvalFlags.TypeExpression).type;
|
||||
return raw ? convertToInstance(raw) : undefined;
|
||||
}
|
||||
|
||||
// 结构化描述类型:联合 → 子类型名列表;单一 → 类名。不依赖 printType 格式。
|
||||
function describeType(type: any): string {
|
||||
if (!type) {
|
||||
return '(undefined)';
|
||||
}
|
||||
if (isUnion(type)) {
|
||||
return type.priv.subtypes.map((sub: any) => describeType(sub)).join(' | ');
|
||||
}
|
||||
if (isClass(type)) {
|
||||
const typeArgs = type.priv.typeArgs;
|
||||
if (typeArgs && typeArgs.length > 0) {
|
||||
return `${type.shared.name}[${typeArgs.map((a: any) => describeType(a)).join(', ')}]`;
|
||||
}
|
||||
return type.shared.name;
|
||||
}
|
||||
return String(type.category);
|
||||
}
|
||||
|
||||
function buildProgram(source: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__type_descriptor__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
fs.mkdirSync(testRoot, { recursive: true });
|
||||
const filePath = path.join(testRoot, 'main.vp');
|
||||
fs.writeFileSync(filePath, source, 'utf8');
|
||||
|
||||
const projectRoot = UriEx.file(testRoot);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[UriEx.file(TRANSPYC_INCLUDES)],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
|
||||
const uri = UriEx.file(filePath);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const parseResults = sourceFile!.getParseResults();
|
||||
return { program, configOptions, parseResults, uri, serviceProvider };
|
||||
}
|
||||
|
||||
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): string[] {
|
||||
return (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
|
||||
(d) => `[category=${d.category}] ${d.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// 收集函数内「变量注解」的解析类型(结构化描述,不依赖 printType 格式)。
|
||||
function collectVarAnnotationTypes(program: Program, parseResults: any): Map<string, string> {
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator);
|
||||
const ev = evaluator;
|
||||
const result = new Map<string, string>();
|
||||
|
||||
class VarCollector extends ParseTreeWalker {
|
||||
override visitAssignment(node: any) {
|
||||
const left = node.d.leftExpr;
|
||||
if (left?.nodeType === ParseNodeType.TypeAnnotation && left.d.annotation) {
|
||||
const name = left.d.valueExpr?.d?.value;
|
||||
if (name) {
|
||||
result.set(name, describeType(evaluateAnnotation(ev, left.d.annotation)));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new VarCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 收集函数内「变量注解」的展示形态(printType,即 hover / 诊断里给用户看的文本)。
|
||||
function collectVarAnnotationDisplays(program: Program, parseResults: any): Map<string, string> {
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator);
|
||||
const ev = evaluator;
|
||||
const result = new Map<string, string>();
|
||||
|
||||
class DisplayCollector extends ParseTreeWalker {
|
||||
override visitAssignment(node: any) {
|
||||
const left = node.d.leftExpr;
|
||||
if (left?.nodeType === ParseNodeType.TypeAnnotation && left.d.annotation) {
|
||||
const name = left.d.valueExpr?.d?.value;
|
||||
if (name) {
|
||||
const type = evaluateAnnotation(ev, left.d.annotation);
|
||||
result.set(name, type ? program.printType(type, { expandTypeAlias: true }) : '(undefined)');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new DisplayCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 收集函数参数 / 返回注解的解析类型。
|
||||
function collectFunctionAnnotationTypes(program: Program, parseResults: any): Map<string, string[]> {
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator);
|
||||
const ev = evaluator;
|
||||
const result = new Map<string, string[]>();
|
||||
|
||||
class FuncCollector extends ParseTreeWalker {
|
||||
override visitFunction(node: FunctionNode) {
|
||||
const funcName = node.d.name.d.value;
|
||||
const list: string[] = [];
|
||||
for (const param of node.d.params) {
|
||||
const ann = (param as any).d?.annotation;
|
||||
if (ann) {
|
||||
list.push(describeType(evaluateAnnotation(ev, ann)));
|
||||
}
|
||||
}
|
||||
if (node.d.returnAnnotation) {
|
||||
list.push('->' + describeType(evaluateAnnotation(ev, node.d.returnAnnotation)));
|
||||
}
|
||||
result.set(funcName, list);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new FuncCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
test('const int / volatile int 注解:保留修饰符且无诊断', () => {
|
||||
const source = `import t
|
||||
|
||||
def f() -> int:
|
||||
a: const int = 5
|
||||
b: volatile int = 6
|
||||
c: t.CConst | int = 5
|
||||
d: t.CVolatile | int = 6
|
||||
return a + b + c + d
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const errors = diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
|
||||
assert.strictEqual(errors.length, 0, `const/volatile 注解不应有错误,实际: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
const types = collectVarAnnotationTypes(program, parseResults!);
|
||||
const aType = types.get('a') ?? '';
|
||||
const bType = types.get('b') ?? '';
|
||||
const cType = types.get('c') ?? '';
|
||||
const dType = types.get('d') ?? '';
|
||||
assert(
|
||||
aType === cType,
|
||||
`\`const int\` 应与手写 \`t.CConst | int\` 完全等价,实际: ${aType} vs ${cType}`
|
||||
);
|
||||
assert(bType === dType, `\`volatile int\` 应与手写 \`t.CVolatile | int\` 完全等价,实际: ${bType} vs ${dType}`);
|
||||
assert(aType.includes('CConst'), `a: const int 应保留 CConst 修饰符,实际: ${aType}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('const/volatile 支持指针、联合与参数/返回值位置', () => {
|
||||
const source = `import t
|
||||
|
||||
def f(p: const t.CPtr, p2: t.CConst | t.CPtr, p3: const int*, p4: t.CConst | int*) -> volatile int:
|
||||
q: const int | t.CPtr = p
|
||||
r: volatile int* = p3
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const errors = diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
|
||||
assert.strictEqual(errors.length, 0, `描述符组合写法不应有错误,实际: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
const funcTypes = collectFunctionAnnotationTypes(program, parseResults!);
|
||||
const fTypes = funcTypes.get('f') ?? [];
|
||||
assert.strictEqual(
|
||||
fTypes[0],
|
||||
fTypes[1],
|
||||
`p: const t.CPtr 应与手写 t.CConst | t.CPtr 完全等价,实际: ${fTypes[0]} vs ${fTypes[1]}`
|
||||
);
|
||||
assert(
|
||||
fTypes[0] && fTypes[0].includes('CConst') && fTypes[0].includes('CPtr'),
|
||||
`p: const t.CPtr 应为 CConst + CPtr,实际: ${fTypes[0]}`
|
||||
);
|
||||
// 指针后缀写法与手写等价(该组合在既有实现中即为 Unknown,此处只保证两者一致,
|
||||
// 不引入新的分叉;是否让 `const T*` 折叠为 CPtr[const T] 见后续讨论)。
|
||||
assert.strictEqual(
|
||||
fTypes[2],
|
||||
fTypes[3],
|
||||
`p: const int* 应与手写 t.CConst | int* 完全等价,实际: ${fTypes[2]} vs ${fTypes[3]}`
|
||||
);
|
||||
assert(
|
||||
fTypes.some((t) => t.startsWith('->') && t.includes('CVolatile')),
|
||||
`-> volatile int 应保留 CVolatile,实际: ${JSON.stringify(fTypes)}`
|
||||
);
|
||||
|
||||
const varTypes = collectVarAnnotationTypes(program, parseResults!);
|
||||
assert(
|
||||
(varTypes.get('q') ?? '').includes('CConst') && (varTypes.get('q') ?? '').includes('CPtr'),
|
||||
`q: const int | t.CPtr 应保留 CConst + CPtr,实际: ${varTypes.get('q')}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('类型描述符不依赖源码 import t(合成 t 静默退化)', () => {
|
||||
const source = `a: const int = 1
|
||||
b: volatile int = 2
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const undefinedDiags = diagnostics.filter((d) => d.includes('未定义') || d.includes('undefined'));
|
||||
assert.strictEqual(
|
||||
undefinedDiags.length,
|
||||
0,
|
||||
`描述符不应要求 import t(合成 t 须静默退化),实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('展示形态:修饰符作为前缀限定符(const rawptr[int]),不暴露联合成员名', () => {
|
||||
const source = `import t
|
||||
|
||||
def f() -> int:
|
||||
a: const int = 5
|
||||
b: volatile int = 6
|
||||
c: const int* = None
|
||||
d: const char* = None
|
||||
e: const volatile int = 7
|
||||
return a + b + e
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const errors = diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
|
||||
assert.strictEqual(errors.length, 0, `描述符注解不应有错误,实际: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
const displays = collectVarAnnotationDisplays(program, parseResults!);
|
||||
assert.strictEqual(displays.get('a'), 'const int', `a 展示形态应为 const int,实际: ${displays.get('a')}`);
|
||||
assert.strictEqual(displays.get('b'), 'volatile int', `b 展示形态应为 volatile int,实际: ${displays.get('b')}`);
|
||||
assert.strictEqual(
|
||||
displays.get('c'),
|
||||
'const rawptr[int]',
|
||||
`c 展示形态应为 const rawptr[int],实际: ${displays.get('c')}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
displays.get('d'),
|
||||
'const rawptr[char]',
|
||||
`d 展示形态应为 const rawptr[char],实际: ${displays.get('d')}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
displays.get('e'),
|
||||
'const volatile int',
|
||||
`e 展示形态应为 const volatile int,实际: ${displays.get('e')}`
|
||||
);
|
||||
|
||||
// 内部表示名(CConst / CVolatile)不得泄漏到展示文本里
|
||||
for (const [name, text] of displays) {
|
||||
assert(
|
||||
!text.includes('CConst') && !text.includes('CVolatile'),
|
||||
`${name} 展示形态不应暴露内部修饰符名,实际: ${text}`
|
||||
);
|
||||
}
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('const / volatile 是硬关键字:只能作类型注解前缀,其余用法报语法错误', () => {
|
||||
const source = `const = 1
|
||||
volatile = 2
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
// 硬关键字:不再是普通标识符,赋值语句必然解析失败。
|
||||
const syntaxErrors = diagnostics.filter((d) => d.startsWith('[category=0]'));
|
||||
assert(
|
||||
syntaxErrors.length > 0,
|
||||
`const/volatile 作普通标识符应报语法错误(硬关键字),实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
// 形状校验:不应再出现左值为 NameNode 'const' / 'volatile' 的普通赋值。
|
||||
const assigned = new Set<string>();
|
||||
class ShapeCollector extends ParseTreeWalker {
|
||||
override visitAssignment(node: any) {
|
||||
const left = node.d.leftExpr;
|
||||
if (left?.nodeType === ParseNodeType.Name) {
|
||||
assigned.add(left.d.value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new ShapeCollector().walk(parseResults!.parserOutput.parseTree);
|
||||
assert(!assigned.has('const'), `\`const = 1\` 不应被解析为普通赋值(硬关键字)`);
|
||||
assert(!assigned.has('volatile'), `\`volatile = 2\` 不应被解析为普通赋值(硬关键字)`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('类型描述符后不跟类型时报语法错误', () => {
|
||||
const source = `import t
|
||||
|
||||
def f() -> int:
|
||||
a: const = 1
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
assert(
|
||||
diagnostics.some((d) => d.includes('类型描述符') && d.includes('const')),
|
||||
`\`a: const\` 应报「类型描述符后必须跟类型」,实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('T* / const T* 不依赖源码 import t', () => {
|
||||
const source = `a: int*
|
||||
b: const int*
|
||||
c: int**
|
||||
d: const int**
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const undefinedDiags = diagnostics.filter((d) => d.includes('未定义') || d.includes('undefined'));
|
||||
assert(
|
||||
!undefinedDiags.some((d) => d.includes('未定义“t”')),
|
||||
`T* / const T* 不应要求 import t(rawptr 内建回退),实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
const errors = diagnostics.filter((d) => d.startsWith('[category=0]'));
|
||||
assert.strictEqual(
|
||||
errors.length,
|
||||
0,
|
||||
`T* / const T* 不应产生错误(未 import t),实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
|
||||
});
|
||||
@@ -46,7 +46,9 @@ import { OperatorType } from '../parser/tokenizerTypes';
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgramForCode(code: string) {
|
||||
// extraModuleFiles:额外写入同目录的模块文件(如 `bootinfo.vp`),并把该目录加入
|
||||
// 额外搜索路径,用于覆盖「import 同目录模块」的场景。不传时行为与原先完全一致。
|
||||
function buildProgramForCode(code: string, extraModuleFiles?: Record<string, string>) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
@@ -54,13 +56,27 @@ function buildProgramForCode(code: string) {
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const fs = require('fs');
|
||||
const testDir = path.join(__dirname, '__unary_ptr__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
if (extraModuleFiles) {
|
||||
for (const [name, content] of Object.entries(extraModuleFiles)) {
|
||||
fs.writeFileSync(path.join(testDir, name), content, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
const extraPaths = [projectRoot];
|
||||
if (extraModuleFiles) {
|
||||
extraPaths.push(UriEx.file(testDir));
|
||||
}
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot],
|
||||
extraPaths,
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
@@ -69,9 +85,6 @@ function buildProgramForCode(code: string) {
|
||||
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, '__unary_ptr__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'unary_ptr.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
@@ -190,10 +203,10 @@ v: t.CInt = sp.a
|
||||
|
||||
def test_interop(x: t.CInt):
|
||||
q: t.CInt* = &x
|
||||
v: t.CInt = *q
|
||||
vq: t.CInt = *q
|
||||
|
||||
def test_double_deref(pp: t.CInt**):
|
||||
v: t.CInt = *(*pp)
|
||||
vpp: t.CInt = *(*pp)
|
||||
|
||||
def make_ptr(x: t.CInt) -> t.CInt*:
|
||||
return &x
|
||||
@@ -612,6 +625,185 @@ def f(ptr_raw: t.CUInt64T, offset: uint64) -> int:
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
// Viper:`T*(x)` 中 T 为「模块名同名类」时的强转。
|
||||
// 背景(用户报的误报):ViperOS VKernel/Kernel/main.vp `BootInfo = bootinfo*(saved_rdi)`,
|
||||
// 其中 `import bootinfo`(模块)与 bootinfo.vp 的 `class bootinfo` 同名。TPC 的 struct
|
||||
// 注册表按短名全局查找,故裸名 bootinfo 取到该结构体类;此前 LSP 把裸名解析成模块,
|
||||
// 报「类型 Module("bootinfo") 和 ulong 不支持运算符 *」。
|
||||
// 锁定:模块内存在同名类时按强转折叠为 CPtr[bootinfo](与限定名 bootinfo.bootinfo* 同型);
|
||||
// 星串两侧带空白的 `plainmod * (v)`(非 C 风格强转写法)仍是真实乘法并报运算符错误。
|
||||
test('Viper T*(x) 强转:T 为「模块名同名类」(import bootinfo + class bootinfo)', () => {
|
||||
const code = `import t
|
||||
from stdint import *
|
||||
import bootinfo
|
||||
import plainmod
|
||||
|
||||
def _start() -> int:
|
||||
saved_rdi: ulong
|
||||
p = bootinfo*(saved_rdi)
|
||||
q = bootinfo.bootinfo*(saved_rdi)
|
||||
r = bootinfo.memory_map_entry*(saved_rdi)
|
||||
return 0
|
||||
|
||||
def _bad() -> int:
|
||||
v: ulong
|
||||
w = plainmod * (v)
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code, {
|
||||
'bootinfo.vp':
|
||||
'class memory_map_entry:\n Base: int = 0\n\n\nclass bootinfo:\n MemmapAddr: int = 0\n',
|
||||
'plainmod.vp': 'X: int = 0\n\ndef helper() -> int:\n return 0\n',
|
||||
});
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const ev = evaluator;
|
||||
|
||||
const rhsTypes: { line: number; type: string }[] = [];
|
||||
class RhsCollector extends ParseTreeWalker {
|
||||
override visitAssignment(node: AssignmentNode) {
|
||||
const line = convertOffsetToPosition(node.d.rightExpr.start, parseResults.parserOutput.lines).line + 1;
|
||||
const type = ev.getType(node.d.rightExpr);
|
||||
rhsTypes.push({ line, type: type ? program.printType(type) : '(undefined)' });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new RhsCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
console.log('=== 模块同名类强转右值类型 ===');
|
||||
rhsTypes.forEach((r) => console.log(` L${r.line}: ${r.type}`));
|
||||
|
||||
// 裸模块名(L8)与限定名(L9)同为 CPtr[bootinfo]
|
||||
for (const line of [8, 9]) {
|
||||
const entry = rhsTypes.find((r) => r.line === line);
|
||||
assert(entry, `强转右值类型未找到 (L${line})`);
|
||||
assert(
|
||||
entry.type.includes('rawptr') && entry.type.includes('bootinfo'),
|
||||
`L${line} 应为 CPtr[bootinfo](rawptr[bootinfo]),实际: ${entry.type}`
|
||||
);
|
||||
}
|
||||
const bareModule = rhsTypes.find((r) => r.line === 8);
|
||||
const qualified = rhsTypes.find((r) => r.line === 9);
|
||||
assert.strictEqual(bareModule?.type, qualified?.type, '裸模块名与限定名强转结果类型应一致');
|
||||
|
||||
// L10:模块内其它类(memory_map_entry)同样可强转
|
||||
const entryType = rhsTypes.find((r) => r.line === 10);
|
||||
assert(
|
||||
entryType && entryType.type.includes('rawptr') && entryType.type.includes('memory_map_entry'),
|
||||
`L10 应为 CPtr[memory_map_entry],实际: ${entryType?.type}`
|
||||
);
|
||||
|
||||
// 诊断:唯一允许的错误是 _bad 里 `plainmod * (v)`(星串两侧有空白 → 仍是真实乘法)
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== 诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` ${d}`));
|
||||
const operatorErrors = diagnostics.filter((d) => d.includes('不支持运算符'));
|
||||
assert.strictEqual(
|
||||
operatorErrors.length,
|
||||
1,
|
||||
`应且仅应报 1 处运算符错误(plainmod * (v) 为真实乘法): ${JSON.stringify(operatorErrors)}`
|
||||
);
|
||||
assert(
|
||||
operatorErrors[0].includes('[line=15]'),
|
||||
`运算符错误应落在 plainmod * (v) 所在行(L15): ${JSON.stringify(operatorErrors)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
// Viper:`T*(x)` 解析为**调用**(与 TPC ExprParser._AtPtrSuffixCall 同形),
|
||||
// 故强转结果可继续参与 trailer:`T*(x)[i] = v` 是合法赋值目标。
|
||||
// 背景(用户报的误报):ViperOS VKernel/Kernel/drivers/core/cpu/cpu.vp:232
|
||||
// `uint32*(info.vendor_id)[0] = res.ebx`
|
||||
// 此前 LSP 把 `*` 当二元乘法(右操作数 `(info.vendor_id)[0]`),整个乘法表达式
|
||||
// 落在赋值左侧 → 报「表达式不能是赋值目标」。
|
||||
// 判据与 TPC 一致:base 是类型原子 + 星串两侧**紧贴**(无空白);带空白的
|
||||
// `a * (b)` / `(a) * (b)` 仍是乘法。
|
||||
test('Viper T*(x)[i] = v:强转结果作为赋值目标(cpu.vp vendor_id 写法)', () => {
|
||||
const code = `import t
|
||||
from stdint import *
|
||||
|
||||
class cpu_info:
|
||||
vendor_id: [13]char
|
||||
|
||||
def get_info(info: cpu_info*, value: uint32) -> int:
|
||||
uint32*(info.vendor_id)[0] = value
|
||||
uint32*(info.vendor_id)[1] = value
|
||||
first: uint32 = uint32*(info.vendor_id)[0]
|
||||
return 0
|
||||
|
||||
def spaced(a: t.CInt, b: t.CInt) -> int:
|
||||
m: t.CInt = a * (b)
|
||||
n: t.CInt = (a) * (b)
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const ev = evaluator;
|
||||
|
||||
// 收集「赋值目标 = 强转调用下标」的形状与强转类型
|
||||
const castTargets: { line: number; funcIsPtrSuffix: boolean; castType: string; targetType: string }[] = [];
|
||||
class CastTargetCollector extends ParseTreeWalker {
|
||||
override visitAssignment(node: AssignmentNode) {
|
||||
const target = node.d.leftExpr;
|
||||
if (target.nodeType === ParseNodeType.Index) {
|
||||
const leftExpr = target.d.leftExpr;
|
||||
if (leftExpr.nodeType === ParseNodeType.Call) {
|
||||
const castType = ev.getType(leftExpr);
|
||||
const targetType = ev.getType(target);
|
||||
castTargets.push({
|
||||
line: convertOffsetToPosition(node.start, parseResults.parserOutput.lines).line + 1,
|
||||
funcIsPtrSuffix: !!(
|
||||
leftExpr.d.leftExpr.nodeType === ParseNodeType.UnaryOperation &&
|
||||
(leftExpr.d.leftExpr as UnaryOperationNode).d.isViperPointerTypeSuffix
|
||||
),
|
||||
castType: castType ? program.printType(castType) : '(undefined)',
|
||||
targetType: targetType ? program.printType(targetType) : '(undefined)',
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new CastTargetCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
console.log('=== T*(x)[i] = v 形状 ===');
|
||||
castTargets.forEach((c) =>
|
||||
console.log(` L${c.line} ptrSuffixFunc=${c.funcIsPtrSuffix} cast=${c.castType} target=${c.targetType}`)
|
||||
);
|
||||
|
||||
// L8/L9:赋值目标为「强转调用 + 下标」,callee 是指针类型后缀
|
||||
for (const line of [8, 9]) {
|
||||
const entry = castTargets.find((c) => c.line === line);
|
||||
assert(entry, `L${line} 赋值目标不是「强转调用下标」形状`);
|
||||
assert(entry.funcIsPtrSuffix, `L${line} 的 callee 应为指针类型后缀节点`);
|
||||
assert(
|
||||
entry.castType.includes('rawptr') && entry.castType.includes('uint32'),
|
||||
`L${line} 强转类型应为 CPtr[uint32],实际: ${entry.castType}`
|
||||
);
|
||||
assert(entry.targetType.includes('uint32'), `L${line} 赋值目标类型应为 uint32,实际: ${entry.targetType}`);
|
||||
}
|
||||
|
||||
// 诊断:`uint32*(info.vendor_id)[0] = value` 不得报「表达式不能是赋值目标」,
|
||||
// 也不得有其它 category=0/1;带空白的乘法仍走真实乘法(此处类型相符,无报错)
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== 诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` ${d}`));
|
||||
const hardErrors = diagnostics.filter((d) => d.includes('category=0') || d.includes('category=1'));
|
||||
assert.strictEqual(
|
||||
hardErrors.length,
|
||||
0,
|
||||
`T*(x)[i] = v 不应产生错误/警告: ${JSON.stringify(hardErrors)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
// Viper:`(T)x` 强转(括号内普通类型名 + 裸值),语义 = 调用形式 `T(x)` 的强转结果。
|
||||
// 与已支持的 `(T)(x)` 调用式强转对照;真实乘法 `(a)*b` / `(a) * (b)` 不受影响。
|
||||
test('Viper (T)x 强转:括号内普通类型名 + 裸值', () => {
|
||||
@@ -844,3 +1036,92 @@ def test_str_arith(fmt: str) -> int:
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
// Viper:双级指针强转 `(T**)x` / `(T**)(x)`(C 风格二级指针),以及 `T**` 注解/下标。
|
||||
// 背景:`T**` 的 AST 是嵌套一元 '*'(*(*T)),值上下文求值时外层 '*' 的操作数已是
|
||||
// 内层求出的 CPtr[T] 实例(非可实例化类),若按「解引用」处理会把括号内类型退化成 T,
|
||||
// 报"T 的对象不可调用"并丢失二级指针语义(用户真实写法:
|
||||
// `slot_l: ast.AST** = (ast.AST**)(uint64(data_l) + li_l * elem_sz_l)`)。
|
||||
// 修复后 parser 标记的指针类型后缀节点(isViperPointerTypeSuffix)在值上下文一律
|
||||
// 按类型表达式求值,故 `(T**)x` 与 `(T**)(x)` 均得到 CPtr[CPtr[T]]。
|
||||
test('Viper 双级指针强转 (T**)x / (T**)(x)', () => {
|
||||
const code = `import t
|
||||
from stdint import *
|
||||
|
||||
class Node:
|
||||
kind: int
|
||||
|
||||
def f(data_l: t.CPtr, li_l: sizet, elem_sz_l: sizet, elem_dst_l: Node*) -> Node*:
|
||||
slot_l: Node** = (Node**)(uint64(data_l) + li_l * elem_sz_l)
|
||||
slot_l[0] = elem_dst_l
|
||||
c1: Node** = (Node**)data_l
|
||||
c2: Node** = (Node**)(data_l)
|
||||
c3 = (Node**)data_l
|
||||
single: Node* = (Node*)data_l
|
||||
return elem_dst_l
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const ev = evaluator;
|
||||
|
||||
const rhsTypes: { line: number; type: string }[] = [];
|
||||
const indexTypes: { line: number; type: string }[] = [];
|
||||
class TypeCollector extends ParseTreeWalker {
|
||||
override visitAssignment(node: AssignmentNode) {
|
||||
const line = convertOffsetToPosition(node.d.rightExpr.start, parseResults.parserOutput.lines).line + 1;
|
||||
const type = ev.getType(node.d.rightExpr);
|
||||
rhsTypes.push({ line, type: type ? program.printType(type) : '(undefined)' });
|
||||
return true;
|
||||
}
|
||||
override visitIndex(node: IndexNode) {
|
||||
const type = ev.getType(node as any);
|
||||
indexTypes.push({
|
||||
line: convertOffsetToPosition(node.start, parseResults.parserOutput.lines).line + 1,
|
||||
type: type ? program.printType(type) : '(undefined)',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new TypeCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
console.log('=== 双级指针强转右值类型 ===');
|
||||
rhsTypes.forEach((r) => console.log(` L${r.line}: ${r.type}`));
|
||||
indexTypes.forEach((i) => console.log(` L${i.line} [0]: ${i.type}`));
|
||||
|
||||
// L8:完整写法 → rawptr[rawptr[Node]]
|
||||
const slotRhs = rhsTypes.find((r) => r.line === 8);
|
||||
assert(slotRhs, `slot_l 右值未找到 (L8)`);
|
||||
assert.strictEqual(slotRhs.type, 'rawptr[rawptr[Node]]', `L8 应为二级指针,实际: ${slotRhs.type}`);
|
||||
|
||||
// L9:`slot_l[0]` → rawptr[Node](一级指针元素)
|
||||
const slotElem = indexTypes.find((i) => i.line === 9);
|
||||
assert(slotElem, `slot_l[0] 未找到 (L9)`);
|
||||
assert.strictEqual(slotElem.type, 'rawptr[Node]', `L9 应为一级指针元素,实际: ${slotElem.type}`);
|
||||
|
||||
// L10/L11/L12:三种二级强转写法 → rawptr[rawptr[Node]]
|
||||
for (const line of [10, 11, 12]) {
|
||||
const entry = rhsTypes.find((r) => r.line === line);
|
||||
assert(entry, `二级强转右值未找到 (L${line})`);
|
||||
assert.strictEqual(entry.type, 'rawptr[rawptr[Node]]', `L${line} 应为二级指针,实际: ${entry.type}`);
|
||||
}
|
||||
|
||||
// L13:一级强转对照 → rawptr[Node]
|
||||
const singleRhs = rhsTypes.find((r) => r.line === 13);
|
||||
assert(singleRhs, `一级强转右值未找到 (L13)`);
|
||||
assert.strictEqual(singleRhs.type, 'rawptr[Node]', `L13 应为一级指针,实际: ${singleRhs.type}`);
|
||||
|
||||
// 不得有 category=0(错误)/ category=1(警告)诊断
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== 诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` ${d}`));
|
||||
const errorsAndWarnings = diagnostics.filter((d) => d.includes('category=0') || d.includes('category=1'));
|
||||
assert(
|
||||
errorsAndWarnings.length === 0,
|
||||
`双级指针强转不应产生错误/警告: ${JSON.stringify(errorsAndWarnings)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
@@ -111,3 +111,76 @@ test('BuddyTest 联合类型变量访问 realloc/alloc 成员不报 UnionType/Un
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
// Viper:`X | t.CPtr` 折叠出的指针级数。
|
||||
// 背景(用户报的误报):`HANDLE = VOIDPTR = t.CPtr`(void*),CreatePipe 的
|
||||
// `hReadPipe: HANDLE | t.CPtr` 语义上是 HANDLE*(void**)。但两侧在类型层同形
|
||||
// (都是 type[CPtr[Unknown]]),原先被并成一级指针 → `&stdout_read`(void**)
|
||||
// 传参被误报"跨级指针隐式转换不允许"。
|
||||
// 本用例锁定:左操作数已是指针时按"包一层指针"折叠;同时真实跨级错误必须照报。
|
||||
test('Viper `X | t.CPtr`:左操作数已是指针时折成二级指针(HANDLE | t.CPtr = void**)', () => {
|
||||
const code = `import t
|
||||
from stdint import *
|
||||
import w32.win32base
|
||||
import w32.win32file
|
||||
|
||||
extern def TakeHandlePtr(h: w32.win32base.HANDLE | t.CPtr) -> BOOL
|
||||
extern def TakeStructPtr(s: w32.win32base.SECURITY_ATTRIBUTES | t.CPtr) -> BOOL
|
||||
extern def TakeVoidPtr(p: t.CVoid | t.CPtr) -> BOOL
|
||||
extern def TakeStrPtr(s: str | t.CPtr) -> BOOL
|
||||
|
||||
def types_ok(handle: w32.win32base.HANDLE, sa: w32.win32base.SECURITY_ATTRIBUTES, text: str) -> int:
|
||||
# HANDLE | t.CPtr = void**:&handle(void**)应接受
|
||||
TakeHandlePtr(&handle)
|
||||
# 结构体 | t.CPtr = 结构体*:&sa 应接受
|
||||
TakeStructPtr(&sa)
|
||||
# CVoid | t.CPtr = void*:&sa(一级)应接受
|
||||
TakeVoidPtr(&sa)
|
||||
# str | t.CPtr = i8**:&text(i8**)应接受
|
||||
TakeStrPtr(&text)
|
||||
return 0
|
||||
|
||||
def level_mismatch_still_reported(handle: w32.win32base.HANDLE) -> int:
|
||||
# void** 传 void*:真实跨级错误必须照报
|
||||
TakeVoidPtr(&handle)
|
||||
return 0
|
||||
|
||||
def create_pipe(capture_output: BOOL, sa: w32.win32base.SECURITY_ATTRIBUTES) -> int:
|
||||
stdout_read: w32.win32base.HANDLE = None
|
||||
stdout_write: w32.win32base.HANDLE = None
|
||||
if capture_output:
|
||||
pipe_result: BOOL = w32.win32file.CreatePipe(&stdout_read, &stdout_write, &sa, 0)
|
||||
return 0
|
||||
`;
|
||||
|
||||
const fs = require('fs');
|
||||
const testDir = path.join(__dirname, '__viper_union_ptr_level__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'ptr_level.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
try {
|
||||
const { program, serviceProvider, uri, configOptions } = buildProgram(testFile);
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== ptr_level.vp diagnostics ===');
|
||||
diagnostics.forEach((d) => console.log(` ${d.range} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const errors = diagnostics.filter((d) => d.category === 0);
|
||||
const levelErrors = errors.filter((d) => d.message.includes('跨级指针'));
|
||||
const callErrors = errors.filter((d) => d.message.includes('CreatePipe') || d.message.includes('TakeHandlePtr')
|
||||
|| d.message.includes('TakeStructPtr') || d.message.includes('TakeStrPtr'));
|
||||
|
||||
// 唯一允许的错误:level_mismatch_still_reported 里 `TakeVoidPtr(&handle)` 的跨级错误。
|
||||
assert.strictEqual(
|
||||
levelErrors.length,
|
||||
1,
|
||||
`应且仅应报 1 处跨级指针错误(void** → void*):${JSON.stringify(errors)}`
|
||||
);
|
||||
assert.strictEqual(callErrors.length, 0, `不应有参数类型误报:${JSON.stringify(callErrors)}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
} finally {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* viperVirtualModifier.test.ts
|
||||
*
|
||||
* 验证 Viper 的 vtable 声明修饰关键字:
|
||||
* virtual class X —— 类级:启用多态 vtable(等价 @t.CVTable class X)
|
||||
* novirtual class X —— 类级:非多态,字段展平嵌入(等价 @t.NoVTable class X)
|
||||
* virtual def f(...) —— 方法级:必须在 class / virtual class 内(不能在 novirtual class 内)
|
||||
* novirtual def f(..) —— 方法级:必须在 class / virtual class / novirtual class 内
|
||||
*
|
||||
* 语义取舍:这些关键字是给编译器的代码生成提示,类型层无额外语义,
|
||||
* 解析器只记录在节点上(ClassNode.d.viperClassModifier / FunctionNode.d.viperModifier),
|
||||
* 并校验方法级修饰的出现位置;语法高亮由 TextMate grammar 负责。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperVirtualModifier.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
|
||||
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';
|
||||
import { ClassNode, FunctionNode } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgram(source: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__virtual_modifier__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
fs.mkdirSync(testRoot, { recursive: true });
|
||||
const filePath = path.join(testRoot, 'main.vp');
|
||||
fs.writeFileSync(filePath, source, 'utf8');
|
||||
|
||||
const projectRoot = UriEx.file(testRoot);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
configOptions.executionEnvironments = [
|
||||
new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[UriEx.file(TRANSPYC_INCLUDES)],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
),
|
||||
];
|
||||
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
const uri = UriEx.file(filePath);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const parseResults = program.getSourceFile(uri)!.getParseResults();
|
||||
const diagnostics = (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
|
||||
(d) => `[category=${d.category}] ${d.message}`
|
||||
);
|
||||
|
||||
return { program, configOptions, parseResults, uri, serviceProvider, diagnostics };
|
||||
}
|
||||
|
||||
function collectClassModifiers(parseResults: any): Map<string, string | undefined> {
|
||||
const result = new Map<string, string | undefined>();
|
||||
class Collector extends ParseTreeWalker {
|
||||
override visitClass(node: ClassNode) {
|
||||
result.set(node.d.name.d.value, node.d.viperClassModifier);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new Collector().walk(parseResults.parserOutput.parseTree);
|
||||
return result;
|
||||
}
|
||||
|
||||
function collectMethodModifiers(parseResults: any): Map<string, string | undefined> {
|
||||
const result = new Map<string, string | undefined>();
|
||||
class Collector extends ParseTreeWalker {
|
||||
override visitFunction(node: FunctionNode) {
|
||||
result.set(node.d.name.d.value, node.d.viperModifier);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new Collector().walk(parseResults.parserOutput.parseTree);
|
||||
return result;
|
||||
}
|
||||
|
||||
function errorsOf(diagnostics: string[]): string[] {
|
||||
return diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
|
||||
}
|
||||
|
||||
test('virtual class / novirtual class 记录修饰形态且不报错', () => {
|
||||
const source = `import t
|
||||
|
||||
virtual class A():
|
||||
val: t.CInt
|
||||
def get(self) -> t.CInt:
|
||||
return self.val
|
||||
|
||||
novirtual class B():
|
||||
val: t.CInt
|
||||
|
||||
class C():
|
||||
val: t.CInt
|
||||
`;
|
||||
const { program, serviceProvider, parseResults, diagnostics } = buildProgram(source);
|
||||
|
||||
const errors = errorsOf(diagnostics);
|
||||
assert.strictEqual(errors.length, 0, `vtable 类声明不应报错,实际: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
const classModifiers = collectClassModifiers(parseResults!);
|
||||
assert.strictEqual(classModifiers.get('A'), 'virtual', `A 应为 virtual class,实际: ${classModifiers.get('A')}`);
|
||||
assert.strictEqual(classModifiers.get('B'), 'novirtual', `B 应为 novirtual class,实际: ${classModifiers.get('B')}`);
|
||||
assert.strictEqual(classModifiers.get('C'), undefined, `C 是普通 class,不应有修饰,实际: ${classModifiers.get('C')}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('virtual def / novirtual def 在允许的类形态内不报错', () => {
|
||||
const source = `import t
|
||||
|
||||
class Plain():
|
||||
virtual def a(self) -> int:
|
||||
return 1
|
||||
novirtual def b(self) -> int:
|
||||
return 2
|
||||
|
||||
virtual class V():
|
||||
virtual def c(self) -> int:
|
||||
return 3
|
||||
novirtual def d(self) -> int:
|
||||
return 4
|
||||
|
||||
novirtual class N():
|
||||
novirtual def e(self) -> int:
|
||||
return 5
|
||||
`;
|
||||
const { program, serviceProvider, parseResults, diagnostics } = buildProgram(source);
|
||||
|
||||
const errors = errorsOf(diagnostics);
|
||||
assert.strictEqual(errors.length, 0, `合法位置的 vtable 方法不应报错,实际: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
const methodModifiers = collectMethodModifiers(parseResults!);
|
||||
for (const [name, expected] of [
|
||||
['a', 'virtual'],
|
||||
['b', 'novirtual'],
|
||||
['c', 'virtual'],
|
||||
['d', 'novirtual'],
|
||||
['e', 'novirtual'],
|
||||
] as const) {
|
||||
assert.strictEqual(
|
||||
methodModifiers.get(name),
|
||||
expected,
|
||||
`${name} 应为 ${expected} 方法,实际: ${methodModifiers.get(name)}`
|
||||
);
|
||||
}
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('virtual def 不能位于 novirtual class 内,也不能位于类外', () => {
|
||||
const source = `import t
|
||||
|
||||
novirtual class N():
|
||||
virtual def bad(self) -> int:
|
||||
return 1
|
||||
|
||||
virtual def topLevel() -> int:
|
||||
return 2
|
||||
|
||||
def outer() -> int:
|
||||
virtual def nested() -> int:
|
||||
return 3
|
||||
return 0
|
||||
`;
|
||||
const { program, serviceProvider, diagnostics } = buildProgram(source);
|
||||
|
||||
assert(
|
||||
diagnostics.some((d) => d.includes('不能位于 novirtual class 内')),
|
||||
`novirtual class 内的 virtual def 应报错,实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
assert(
|
||||
diagnostics.filter((d) => d.includes('"virtual def" 必须位于 class 或 virtual class 内')).length >= 2,
|
||||
`类外的 virtual def(模块级 / 函数体内)应各报一次,实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('novirtual def 位于类外时报错', () => {
|
||||
const source = `import t
|
||||
|
||||
novirtual def topLevel() -> int:
|
||||
return 1
|
||||
`;
|
||||
const { program, serviceProvider, diagnostics } = buildProgram(source);
|
||||
|
||||
assert(
|
||||
diagnostics.some((d) => d.includes('"novirtual def" 必须位于 class、virtual class 或 novirtual class 内')),
|
||||
`类外的 novirtual def 应报错,实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('virtual / novirtual 不搭配 class、def 时报错', () => {
|
||||
const source = `import t
|
||||
|
||||
virtual val: t.CInt
|
||||
|
||||
novirtual def f(x: t.CInt) -> t.CInt:
|
||||
return x
|
||||
`;
|
||||
const { program, serviceProvider, diagnostics } = buildProgram(source);
|
||||
|
||||
assert(
|
||||
diagnostics.some((d) => d.includes('必须与 class 或 def 搭配使用')),
|
||||
`virtual 不搭配 class/def 应报错,实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('装饰器后的 virtual class 同样成立,且类成员可正常访问', () => {
|
||||
const source = `import t
|
||||
|
||||
@t.Object
|
||||
virtual class Widget():
|
||||
val: t.CInt
|
||||
def __init__(self, v: t.CInt):
|
||||
self.val = v
|
||||
def get(self) -> t.CInt:
|
||||
return self.val
|
||||
|
||||
export def main() -> int:
|
||||
w = Widget(7)
|
||||
return w.get()
|
||||
`;
|
||||
const { program, serviceProvider, parseResults, diagnostics } = buildProgram(source);
|
||||
|
||||
const errors = errorsOf(diagnostics);
|
||||
assert.strictEqual(errors.length, 0, `装饰器 + virtual class 不应报错,实际: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
const classModifiers = collectClassModifiers(parseResults!);
|
||||
assert.strictEqual(classModifiers.get('Widget'), 'virtual', `Widget 应为 virtual class,实际: ${classModifiers.get('Widget')}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
|
||||
});
|
||||
@@ -31,11 +31,17 @@ from _typeshed import (
|
||||
SupportsRichComparisonT,
|
||||
SupportsWrite,
|
||||
)
|
||||
from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized
|
||||
from collections.abc import Awaitable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized
|
||||
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
|
||||
from os import PathLike
|
||||
from types import CellType, CodeType, EllipsisType, GenericAlias, NotImplementedType, TracebackType
|
||||
|
||||
# Viper 扩展:ASM_DESCR 免 `t.` 前缀直接可用 —— 内联汇编的约束 / 破坏描述符常量类
|
||||
# (等价 t.ASM_DESCR,见本文件末尾 __asm__ / asminp / asmout 一节)。
|
||||
# 写成 `X as X` 的冗余别名形式:stub 文件里 `from m import X` 默认不对外再导出,
|
||||
# 只有冗余别名才构成显式 re-export(PEP 484 对 .pyi 的规则)。
|
||||
from t import ASM_DESCR as ASM_DESCR
|
||||
|
||||
# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping}
|
||||
# are imported from collections.abc in builtins.pyi
|
||||
from typing import ( # noqa: Y022,UP035
|
||||
@@ -2734,6 +2740,49 @@ def LOut(expr: Any, constraint: Any = None) -> Any:
|
||||
"""LLVM IR 输出操作数标记(等价 c.LOut)。"""
|
||||
...
|
||||
|
||||
# --- Viper 扩展:C 风格内置 asm 助记符(免 c./t. 前缀) ---
|
||||
# 与 C 的写法对齐,源码里可直接写 `__asm__(...)` / `asminp(...)` / `asmout(...)` /
|
||||
# `ASM_DESCR.XXX`,不必 `import c` 或加 `t.` 前缀:
|
||||
# - `__asm__` 等价 c.Asm(声明式内联汇编,参数为模板串 + inp/out/op 关键字);
|
||||
# - `asminp` / `asmout` 等价 c.AsmInp / c.AsmOut(操作数标记,constraint 取
|
||||
# ASM_DESCR 上的约束常量);
|
||||
# - `ASM_DESCR` 见本文件顶部 `from t import ASM_DESCR`。
|
||||
# 原有 Asm / AsmInp / AsmOut(以及 c.* / t.ASM_DESCR)写法保持可用。
|
||||
|
||||
def __asm__(asm: str, *args: Any, inp: Any = None, out: Any = None, op: Any = None) -> Any:
|
||||
"""内联汇编(等价 c.Asm)。inp 为输入操作数描述符列表,out 为输出操作数
|
||||
描述符列表,op 为破坏描述符列表。"""
|
||||
...
|
||||
|
||||
def asminp(expr: Any, constraint: Any = None) -> Any:
|
||||
"""汇编输入操作数标记(等价 c.AsmInp)。constraint 为寄存器约束
|
||||
(如 ASM_DESCR.REG_ANY)。"""
|
||||
...
|
||||
|
||||
def asmout(expr: Any, constraint: Any = None) -> Any:
|
||||
"""汇编输出操作数标记(等价 c.AsmOut)。constraint 为寄存器约束
|
||||
(如 ASM_DESCR.OUTPUT_REG)。"""
|
||||
...
|
||||
|
||||
# --- Viper 扩展:LLVM IR 内置助记符(免 c. 前缀) ---
|
||||
# 与 C 风格 asm 助记符同一套机制,只是针对内联 LLVM IR:
|
||||
# - `__llvmir__` 等价 c.LLVMIR(模板串 + 可选结果类型等位置参数 + inp/out/op);
|
||||
# - `irinp` / `irout` 等价 c.LInp / c.LOut(操作数标记)。
|
||||
# 原有 LLVMIR / LInp / LOut(以及 c.*)写法保持可用。
|
||||
|
||||
def __llvmir__(ir: str, *args: Any, inp: Any = None, out: Any = None, op: Any = None) -> Any:
|
||||
"""内联 LLVM IR(等价 c.LLVMIR)。ir 为 IR 模板串,其后可跟结果类型等位置参数;
|
||||
inp/out/op 为操作数描述符列表。"""
|
||||
...
|
||||
|
||||
def irinp(expr: Any, constraint: Any = None) -> Any:
|
||||
"""LLVM IR 输入操作数标记(等价 c.LInp)。"""
|
||||
...
|
||||
|
||||
def irout(expr: Any, constraint: Any = None) -> Any:
|
||||
"""LLVM IR 输出操作数标记(等价 c.LOut)。"""
|
||||
...
|
||||
|
||||
# --- Viper 扩展:编译器内置裸指针类型 rawptr ---
|
||||
# rawptr 是编译器内置类型(TPC/TPV 双端,语法保留字,不可重定义):
|
||||
# rawptr[T] 固定表示"裸指针,指向原始类型 T"(等价 t.CPtr[T]),
|
||||
@@ -2743,6 +2792,23 @@ def LOut(expr: Any, constraint: Any = None) -> Any:
|
||||
# 路径并忽略本声明(见 TransPyC TypeAnnotationResolver / SymbolExtractor)。
|
||||
rawptr: t.CTypedef = t.CPtr
|
||||
|
||||
# --- Viper 扩展:类型描述符 const / volatile 的脱糖目标 ---
|
||||
# `const T` / `volatile T` 由 parser 脱糖为 `CConst | T` / `CVolatile | T`
|
||||
# (见 parser._parseViperTypeDescriptorPrefix)。这两个名字声明在 builtins stub 中,
|
||||
# 使脱糖结果对任意 Viper 工程均可解析 —— 与 T* 指针注解一致,不要求源码
|
||||
# 显式 `import t`(t.CConst / t.CVolatile 需要 import t 才可见)。
|
||||
# 值语义即 t.CConst / t.CVolatile 本身(TypeSet 修饰符,不改变数据类型)。
|
||||
CConst: t.CTypedef = t.CConst
|
||||
CVolatile: t.CTypedef = t.CVolatile
|
||||
|
||||
# --- Viper 扩展:注解位置的小写 callable 等价 t.Callable ---
|
||||
# `callable[[A, B], R]` / `callable[..., R]` / `callable[[], R]` 由 parser 在
|
||||
# **注解位置**脱糖为同名大写的 `Callable[...]`(见 parser._parseAtom),
|
||||
# 值位置的 `callable(x)` 仍是内置函数(本别名只在类型位置生效)。
|
||||
# `Callable` 在此声明,使脱糖结果对任意 Viper 工程均可解析 —— 与 rawptr /
|
||||
# CConst 一致,不要求源码显式 `import t`(t.Callable 需要 import t 才可见)。
|
||||
Callable: t.CTypedef = t.Callable
|
||||
|
||||
# --- Viper 扩展:C 定宽/最快/最小整型 typedef(镜像 TransPyC includes/builtins.vp) ---
|
||||
# 这些 typedef 由编译器内置的 includes/builtins.vp 提供。LSP 侧原先只靠
|
||||
# program.ts 的 _mergeViperBuiltinsVpSymbols 在"被分析文件所在目录及其祖先目录的
|
||||
@@ -2756,6 +2822,8 @@ rawptr: t.CTypedef = t.CPtr
|
||||
# 语言内置名称,无需 import)。C 的 "long long" / "unsigned long long" 含空格、
|
||||
# 不能作标识符,Viper 写作 longlong / ulonglong。
|
||||
short: t.CTypedef = t.CShort
|
||||
uint: t.CTypedef = t.CUnsignedInt
|
||||
uchar: t.CTypedef = t.CUnsignedChar
|
||||
long: t.CTypedef = t.CLong
|
||||
longlong: t.CTypedef = t.CLongLong
|
||||
ushort: t.CTypedef = t.CUnsignedShort
|
||||
|
||||
@@ -400,6 +400,17 @@ class CVolatile(CType):
|
||||
pass
|
||||
|
||||
|
||||
class CAttr(CType):
|
||||
"""`__attribute__` 属性标记(编译期元数据,不改变数据类型)。
|
||||
|
||||
与 CConst / CVolatile 同族的修饰符:注解里写 `T | t.attr.section(".x")`
|
||||
(编译器按 AST 扫描提取属性,见 t.attr)或 `__attribute__(section(".x")) T`
|
||||
时,LSP 把属性标记求值为本类的实例——它只作修饰,不参与类型检查:
|
||||
运算/下标/赋值都按被修饰的基础类型处理,展示时不作为联合成员暴露。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CStatic(CType):
|
||||
"""static 存储类。"""
|
||||
pass
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vipeye",
|
||||
"version": "1.1.425",
|
||||
"version": "1.1.426",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vipeye",
|
||||
"version": "1.1.425",
|
||||
"version": "1.1.426",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vipeye": "index.js",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "vipeye",
|
||||
"displayName": "Vipeye",
|
||||
"description": "Type checker for the Viper language",
|
||||
"version": "1.1.425",
|
||||
"version": "1.1.426",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "TermiNexus"
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# Change Log
|
||||
|
||||
## 1.1.426
|
||||
|
||||
- **Type descriptors**: `const` / `volatile` may prefix any type annotation — `a: const int`, `b: volatile int`, `def f() -> volatile int`, `p: const t.CPtr`, `q: const int*`, in parameters, return types, class members and type aliases. Both are **hard keywords** now (reserved words): the only legal use is a type-annotation prefix, anything else (`const = 1`, `a: const` with no type) is reported as a syntax error with a dedicated message. They desugar in the parser into the equivalent type-set annotation `CConst | T` / `CVolatile | T`, so they weigh exactly the same as the hand-written form in assignment compatibility, operator handling and diagnostics — no second modifier mechanism. The desugared `CConst` / `CVolatile` names live in the built-in `builtins.vpi` stub, so no `import t` is required
|
||||
- `const T*` is now `(CConst | T)*` → `CConst | CPtr[T]`, i.e. a pointer to the described type, matching the `CConst | CChar | CPtr` (const char *) spelling used by TransPyC. The pointer suffix is always applied at the outermost level, so the result no longer depends on whether the `*` is followed by an end-of-line or by more source (a newline used to make the star attach at a different level, leaving the annotation as `Unknown`)
|
||||
- `T*` / `&x` no longer require `import t`: when the file doesn't import the `t` module, `CPtr` falls back to the compiler built-in `rawptr` from `builtins.vpi`. Previously such files got `type[int] is not supported for operator "*"` and an `Unknown` type
|
||||
- `|` combinations that mix a type descriptor with an **instance**-form type (e.g. `t.CConst | int*`, whose right operand evaluates to `CPtr[int]`) are now treated as a type-set union instead of falling through to `__or__` and degrading to `Unknown`
|
||||
- Type descriptors are rendered as a **prefix qualifier** instead of a union member, so the internal representation never leaks into hovers or diagnostics: `a: const int*` shows `const rawptr[int]` (not `CConst | rawptr[int]`), `a: const int` shows `const int`, and `a: const volatile int` shows `const volatile int`. Storage-class descriptors get the same treatment (`static` / `register` / `auto` / `inline` / `extern`)
|
||||
- Type descriptors no longer appear as the pointee of a pointer: `(CConst | int)*` evaluates to `CConst | CPtr[int]` rather than the "union of pointers" `CPtr[CConst] | CPtr[int]`
|
||||
- `t.CVolatile` is handled everywhere `t.CConst` already was (type-set subscript, iteration, member access, string-literal assignability, `Struct | CPtr` folding), so `volatile` behaves symmetrically with `const`
|
||||
- New **`inline while cond:` / `inline for x in y:`**: the `inline` modifier is accepted on loop statements (it is a code-generation hint, so no additional type semantics — the loop is parsed as a plain `while` / `for`), and `inline` is highlighted as a modifier. Using `inline` anywhere else still reports "must be used with 'def'"
|
||||
- `const` / `volatile` are highlighted as type modifiers
|
||||
- New built-in C basic type names `uint` (`t.CUnsignedInt`) and `uchar` (`t.CUnsignedChar`), declared in the built-in `builtins.vpi` stub. Any Viper project resolves them without configuring `includes` — `uint` already exists in TransPyC's `includes/builtins.vp` but was missing from the stub, so it was reported as undefined in setups where that file cannot be located; `uchar` is new in both places
|
||||
- New **`virtual` / `novirtual` declaration modifiers** for vtable control, equivalent to the `@t.CVTable` / `@t.NoVTable` decorators: `virtual class X` (polymorphic vtable) and `novirtual class X` (flattened, no vtable) are recorded on the class node, and `virtual def f(...)` / `novirtual def f(...)` mark individual methods. Placement is enforced: `virtual def` must be inside a `class` or `virtual class` (rejected inside a `novirtual class`, at module level, or nested in a function body), and `novirtual def` must be inside a class of any of the three forms. Using either modifier without `class` / `def` reports a dedicated error, they work after decorators too, and both are highlighted as type modifiers
|
||||
- Two-level pointer casts work now: `(T**)x` / `(T**)(x)` evaluate to `CPtr[CPtr[T]]` (e.g. `slot_l: ast.AST** = (ast.AST**)(uint64(data_l) + li_l * elem_sz_l)`, whose element access `slot_l[0]` yields `ast.AST*`). The `T**` AST is a nested unary `*` pair whose outer operand is already the `CPtr[T]` instance produced by the inner one; in a value context that was read as a **dereference**, collapsing the cast target to `T` (reported as "object of type 'T' is not callable") and losing the second pointer level. Pointer-type suffixes marked by the parser are now always evaluated as type expressions, so `(T*)x` / `T*(x)` are unaffected
|
||||
- Bare C-style inline-asm and LLVM-IR builtins, so the C spelling can be used verbatim without any import or module prefix: **`__asm__(...)`** (same as `c.Asm` / `Asm`), **`asminp(...)`** / **`asmout(...)`** (same as `c.AsmInp` / `c.AsmOut`), **`__llvmir__(...)`** (same as `c.LLVMIR` / `LLVMIR`) and **`irinp(...)`** / **`irout(...)`** (same as `c.LInp` / `c.LOut`), plus **`ASM_DESCR`** (same as `t.ASM_DESCR`, so `ASM_DESCR.REG_ANY` / `ASM_DESCR.CLOBBER_RDX` resolve). All of these are declared in the built-in `builtins.vpi` stub except `ASM_DESCR`, which is re-exported there as `from t import ASM_DESCR as ASM_DESCR` (a stub re-export must use the redundant-alias form). The existing `Asm` / `AsmInp` / `AsmOut` / `LLVMIR` / `LInp` / `LOut` names and the `c.*` / `t.ASM_DESCR` spellings keep working
|
||||
- `X | t.CPtr` now folds to a pointer-to-`X` even when `X` is itself a pointer: `HANDLE | t.CPtr` (with `HANDLE = VOIDPTR = t.CPtr`, i.e. void*) is `void**`, not `void*`. Both operands used to evaluate to the very same type (`type[CPtr[Unknown]]`), so the union collapsed into a single-level pointer and `&handle` (void**) was rejected — e.g. `w32.win32file.CreatePipe(&stdout_read, &stdout_write, &sa, 0)`, whose parameters are declared `HANDLE | t.CPtr`, reported "跨级指针隐式转换不允许" (a false positive: `LPHANDLE` really is `HANDLE*`). `createUnionType` now decides this syntactically: when the right operand is the bare `CPtr` / `rawptr` class and the left operand already is a pointer type, the left operand is wrapped (`CPtr[X]`). Struct / void / str operands keep their previous behavior (`Scheduler | t.CPtr` → `CPtr[Scheduler]`, `CVoid | t.CPtr` → void*, `str | t.CPtr` → i8**), and genuine level mismatches (void** → void*) are still reported
|
||||
- Viper's own keywords are now offered by completion: the hard keywords **`define`** (macro function, same as `def`), **`const`** / **`volatile`** (type-descriptor prefixes) and the soft keywords **`typedef`**, **`inline`** / **`export`** / **`extern`** / **`virtual`** / **`novirtual`**, **`enum`** / **`rnum`** / **`union`** / **`struct`**, plus the `def` aliases **`fn`** / **`fun`** / **`func`** / **`function`**. They are absent from CPython's keyword table, so they used to be invisible in the suggestion list even though the parser accepts them (the TextMate grammar already highlighted them). `completionProvider`'s `Keywords` namespace now merges a Viper list into both the full and the expression-only keyword sets
|
||||
- New **"Count Project LOC"** command (`viper.countLines`): right-click a `.vpj` in the Explorer → the project's `source_dir` (usually `App`) is scanned recursively and every **known language** is counted. A **`Viper LOC` Webview panel** opens *before* the scan and shows a **determinate progress bar** (files collected first, then processed `N / M` with the current file), and once done renders the report: KPI cards (files, total / code / comment / blank lines, **comment rate**, **average line length**, characters), a **stacked bar chart** per language (code / comment / blank segments, scaled to the largest language), a **language filter** (checkboxes that re-compute the KPIs, chart and table), and a detail table with a totals row. The same report is also written in plain text to the `Viper LOC` output channel for copying. Known languages follow the extensions this extension registers (`.vp`, `.vpi`, `.vpnci`, `.vpj`) and, as requested, `.def` files are supported too (module-definition `;` line comments, C-style `//` line comments and slash-star block comments are all recognized; block comments that span lines are counted as comments). The project's `build_dir`, hidden directories, `node_modules`, `.git` and `__pycache__` are skipped, and symlinks are not followed
|
||||
- `T*(x)` casts now accept a bare name that resolves to a **module** when that module defines a class with the same name, as in `import bootinfo` plus `bootinfo.vp`'s `class bootinfo` — the spelling ViperOS uses for `BootInfo = bootinfo*(saved_rdi)` in `VKernel/Kernel/main.vp`. TransPyC's struct registry is keyed by short name, so the plain name picks up the class there; Vipeye evaluated it as the module instead, the cast fell through to arithmetic and reported `类型"Module("bootinfo")"和"ulong"不支持运算符"*"`. It now folds to `CPtr[bootinfo]`, identical to the qualified `bootinfo.bootinfo*(saved_rdi)` / `bootinfo.memory_map_entry*(saved_rdi)` spellings. A module without a same-named class is not turned into a cast, so the genuine operator error is still reported
|
||||
- `T*(x)` is now parsed as a **call** (`T*(x)[i]` / `T*(x).f` therefore work), matching TransPyC's `_AtPtrSuffixCall`: when a type atom (`Name` / `member access` / `subscript`) is followed by a run of `*`/`**` and then `(` with **no whitespace anywhere in between**, the run is consumed as a pointer-type suffix and the `(` becomes the call trailer — the same tree `(T*)x` already produces. This fixes `uint32*(info.vendor_id)[0] = res.ebx` in ViperOS `drivers/core/cpu/cpu.vp`, which was read as `uint32 * (info.vendor_id[0])` and reported "表达式不能是赋值目标" (the whole multiplication sat on the left of `=`); the cast now yields `CPtr[uint32]`, the subscript yields `uint32`, and the assignment is accepted. Real multiplications keep multiplying: `a * (b)`, `(a) * (b)`, `(a)*b`, `n*(m - 1)` and `2 * 3` are unaffected (whitespace or a non-type-atom base), and `(T*)[i]` stays a subscript
|
||||
- **Duplicate declaration** of a module-level variable inside a function is now an **error (TypeError)**, matching TransPyV's `AssignGenMixin` check: when the module already declares a runtime variable `x` (`x: T = ...`), a **type-annotated** declaration of the same name inside a function fails *unconditionally* — both without a `global` statement (it would shadow the global as a local) and **with** one (the annotation duplicates the existing global declaration). Type equality is deliberately not considered. The only legal spellings stay: `global x` + an **un-annotated** `x = v` assignment, or a differently-named local. Scope is function-only — module-level re-annotation, class members, `CDefine` macro constants (no global storage), `CTypedef` aliases and same-named functions/classes are not flagged; the diagnostic is only produced when the name really is a module-level variable. Verified against the real tree: `TransPyV/App`, `VET/App` and `includes` (147 files) have **zero** violations, while the TPC-compiled ViperOS kernel has 14 in `VKernel/Kernel/paging/paging.vp` (L99–L190 re-annotate `pdp` / `pd` / `pt` right after `global pdp, pd, pt`), which TransPyV rejects as well
|
||||
- **`__attribute__`** is now supported in both spellings Viper uses (LSP side only; TransPyC/TransPyV are unchanged). As a **decorator**, `@__attribute__(section(".text.wo"), always_inline)` desugars into `@c.Attribute(t.attr.section(".text.wo"), t.attr.always_inline)` — the same tree TransPyC's `_DesugarMagicAttribute` produces, so it works for `class` and `def` alike and no second mechanism is introduced. As a **modifier**, `a: __attribute__(section(".x")) const int = 1` desugars into the type-set annotation `t.attr.section(".x") | (CConst | int)`, i.e. the same family as `const` / `volatile` (it must be followed by a type, one layer of parentheses only — not GCC's `__attribute__((...))`), and it composes freely with `const` / `volatile` in either order. Attribute names in the parentheses are offered by completion, listed from the `attr` class declared in the `t` stub
|
||||
- Attribute markers in a type annotation (`T | t.attr.section(".x")`, `T | t.attr.llvm.nounwind`, and the markers the parser synthesizes from `__attribute__(...)`) are now evaluated as the modifier `t.CAttr` instead of leaking into the annotation as a union member. Previously `t.attr.llvm.nounwind` stayed the class `nounwind` — itself a union member, so `P: t.CInt | t.attr.llvm.nounwind | t.attr.llvm.nobuiltin` displayed as `CInt | nounwind | nobuiltin`, `p + 1` reported "运算符不支持" and `V[0]` reported "类型"nounwind"上未定义"__getitem__""; and `t.attr.section(".x")` (a *call*) was rejected with "类型表达式中不允许使用调用表达式". `CAttr` joins the existing C-modifier family, so operators, subscripts, assignment compatibility and rendering all skip it by the same rules as `CConst` / `CVolatile`, and the property names (`section`, `always_inline`, `nounwind`, …) resolve against the `t` stub for jump-to-definition and hover
|
||||
- New **value-position `volatile` prefix**: `a: int = volatile f()` and `b: Settings = volatile Settings()` mark a call (function call or struct construction) as volatile for code generation — the expression keeps the call's own type, TransPyC / TransPyV are not involved. Only a call may follow it (one is reported otherwise), and the bare uses `volatile = 2` / `const = 1` still report a syntax error
|
||||
- `__attribute__` is highlighted as a type modifier (same scope as `const` / `volatile`): the TextMate grammar matches it when followed by `(`, in both the decorator and the modifier spelling
|
||||
- `__attribute__` is also offered by completion (typing `__a` lists it) and **hovering inside `__attribute__(...)`** shows the full list of available attribute names (`section` / `aligned` / `always_inline` / `weak` / …), taken from the `attr` class of the `t` stub. Both share one helper (`viperAttributeUtils`) for locating the parentheses and enumerating `t.attr`, so the completion list and the hover list cannot drift apart
|
||||
- Lowercase **`callable[[A, B], R]`** is now supported in a type annotation and means exactly `t.Callable[[A, B], R]` — all three spellings (`callable[...]`, `Callable[...]`, `t.Callable[...]`) and all the usual argument forms (`callable[[int, str], bool]`, `callable[..., bool]`, `callable[[], bool]`, as a parameter, return or variable annotation) evaluate to the same callable type. The parser rewrites the bare name `callable` to `Callable` **only in type-annotation position**, so the built-in function is untouched: `callable(x)` / `if callable(x):` still behave as before. `Callable` is declared in the built-in `builtins.vpi` stub (`Callable: t.CTypedef = t.Callable`, next to `rawptr` / `CConst`), so no `import t` is required. Previously `callable[[int], bool]` silently degraded to the type of the `callable` *function* (the Viper-dialect rule that ignores explicit type arguments on a callable), and bare `Callable` was reported as undefined
|
||||
|
||||
## 1.1.425
|
||||
|
||||
- New `typedef T1 = T2` statement, fully equivalent to `T1: t.CTypedef = T2`. It desugars in the parser into the same annotated-assignment tree (synthetic zero-length `t.CTypedef`), so binding, type evaluation, hover and semantic highlighting all behave identically — and it works without the file having to `import t`. `typedef` is a soft keyword, so `typedef = 1` / `typedef(x)` still parse as before. `typedef` is also highlighted as a declaration keyword now
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vscode-vipeye",
|
||||
"version": "1.1.425",
|
||||
"version": "1.1.426",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vscode-vipeye",
|
||||
"version": "1.1.425",
|
||||
"version": "1.1.426",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mermaid": "^11.12.0",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "vipeye",
|
||||
"name": "vscode-vipeye",
|
||||
"displayName": "Vipeye",
|
||||
"description": "VS Code language support for Viper (LSP client)",
|
||||
"version": "1.1.425",
|
||||
"version": "1.1.426",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
@@ -330,6 +330,11 @@
|
||||
"command": "viper.openVpjAsJson",
|
||||
"title": "Open .vpj as JSON",
|
||||
"category": "Viper"
|
||||
},
|
||||
{
|
||||
"command": "viper.countLines",
|
||||
"title": "Count Project LOC",
|
||||
"category": "Viper"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
@@ -390,6 +395,10 @@
|
||||
{
|
||||
"command": "viper.openVpjAsJson",
|
||||
"when": "resourceExtname == .vpj"
|
||||
},
|
||||
{
|
||||
"command": "viper.countLines",
|
||||
"when": "resourceExtname == .vpj"
|
||||
}
|
||||
],
|
||||
"editor/title/context": [
|
||||
@@ -404,6 +413,11 @@
|
||||
"command": "viper.openVpjAsJson",
|
||||
"when": "resourceExtname == .vpj",
|
||||
"group": "1_open"
|
||||
},
|
||||
{
|
||||
"command": "viper.countLines",
|
||||
"when": "resourceExtname == .vpj",
|
||||
"group": "1_open@2"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
|
||||
@@ -47,7 +47,19 @@
|
||||
},
|
||||
{
|
||||
"name": "storage.modifier.viper",
|
||||
"match": "\\b(inline|export|extern)\\b(?=\\s+(?:def|fn|fun|func|function)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*|[ \\t]*$)"
|
||||
"match": "\\b(inline|export|extern)\\b(?=\\s+(?:def|fn|fun|func|function|while|for)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*|[ \\t]*$)"
|
||||
},
|
||||
{
|
||||
"name": "storage.modifier.viper",
|
||||
"match": "\\b(virtual|novirtual)\\b(?=\\s+(?:class|def|fn|fun|func|function)\\b)"
|
||||
},
|
||||
{
|
||||
"name": "storage.modifier.viper",
|
||||
"match": "\\b(const|volatile)\\b(?=\\s+[A-Za-z_][A-Za-z0-9_]*|\\s*\\[)"
|
||||
},
|
||||
{
|
||||
"name": "storage.modifier.viper",
|
||||
"match": "\\b__attribute__(?=\\s*\\()"
|
||||
},
|
||||
{
|
||||
"name": "storage.type.class.viper",
|
||||
|
||||
Reference in new issue
Block a user