feat(vipeye-internal): 支持 enum X: 枚举语法与 define 常量声明

- enum:`enum NAME:` 识别为 Viper 枚举(等价 class NAME(t.CEnum)),
  成员支持裸名与逗号分隔(enum X: a, b, c),并支持同行声明后换行 TAB 续写
- enum:取值按编译期规则推导(从 0 递增,中间显式赋值后从该值 +1 续推),
  hover 显示 (variable) IsAsync: ASTFlag = 0,成员可隐式转换为 int
- enum:成员推导值超出声明类型位宽时报错(如 i1 存不下 2)
- define:`define NAME: 类型 = 值` / `define NAME = 值` 解析为常量声明,
  `define f(...)` 宏函数行为不变
- 新增整数字面量位宽校验:字面量无法存入声明类型时提示扩容,
  define 与普通声明均生效(位宽表源自 wiki 02-type-system.md)
- 新增 viperEnumFlag.test.ts(5 项)与 viperDefineConst.test.ts(4 项)
This commit is contained in:
TermiNexus committed 2026-09-21 23:13:38 +08:00
1 parent 70a4405294
commit 525a654f5c
24 files changed
+1272 -16

No files matched your search

@@ -547,6 +547,13 @@ export class Binder extends ParseTreeWalker {
this._dunderSlotsEntries = undefined;
if (!this._moduleSymbolOnly) {
// Viper 扩展:`enum X:` 的成员允许只写成员名(裸名 / 逗号分隔的裸名),
// 这不是赋值语句,Python 规则下不会产生声明。
// 这里先补出类作用域声明,使成员访问、语义高亮与枚举语义可用。
if (node.d.isViperEnum) {
this._bindViperEnumMemberNames(node.d.suite);
}
// Analyze the suite.
this.walk(node.d.suite);
}
@@ -3772,6 +3779,53 @@ export class Binder extends ParseTreeWalker {
return undefined;
}
// Viper 扩展:`enum X:` 的成员允许只写成员名:
// enum ASTFlag:
// IsAsync
// Simple
// HasStar: int
// enum X: a, b, c
// 裸名(含逗号分隔的裸名元组)在 Python 规则下只是表达式语句,不产生声明,
// 这里为它们补出类作用域声明;取值由编译期推导(见 checker 的枚举取值校验)。
private _bindViperEnumMemberNames(suite: SuiteNode): void {
suite.d.statements.forEach((statement) => {
if (statement.nodeType !== ParseNodeType.StatementList) {
return;
}
statement.d.statements.forEach((subStatement) => {
this._bindViperEnumMemberNameNode(subStatement);
});
});
}
private _bindViperEnumMemberNameNode(node: ParseNode): void {
if (node.nodeType === ParseNodeType.Name) {
this._addViperEnumMemberDeclaration(node);
} else if (node.nodeType === ParseNodeType.Tuple) {
node.d.items.forEach((item) => this._bindViperEnumMemberNameNode(item));
}
}
private _addViperEnumMemberDeclaration(node: NameNode): void {
const symbol = this._bindNameToScope(this._currentScope, node);
if (!symbol) {
return;
}
const declaration: VariableDeclaration = {
type: DeclarationType.Variable,
node,
isConstant: isConstantName(node.d.value),
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(node, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
isExplicitBinding: this._currentScope.getBindingType(node.d.value) !== undefined,
};
symbol.addDeclaration(declaration);
}
private _bindPossibleTupleNamedTarget(target: ExpressionNode, addedSymbols?: Map<string, Symbol>) {
switch (target.nodeType) {
case ParseNodeType.Name: {
@@ -99,7 +99,13 @@ import { addInheritedDataClassEntries } from './dataClasses';
import { Declaration, DeclarationType, isAliasDeclaration, isVariableDeclaration } from './declaration';
import { getNameNodeForDeclaration } from './declarationUtils';
import { getDeprecatedAliases, getDeprecatedSpecialForms } from './deprecatedSymbols';
import { getEnumDeclaredValueType, isEnumClassWithMembers, transformTypeForEnumMember } from './enums';
import {
getEnumDeclaredValueType,
getViperEnumMemberValues,
getViperIntegerLiteralValue,
isEnumClassWithMembers,
transformTypeForEnumMember,
} from './enums';
import { ImportResolver, createImportedModuleDescriptor } from './importResolver';
import { ImportResult, ImportType } from './importResult';
import { getRelativeModuleName, getTopLevelImports } from './importStatementUtils';
@@ -411,6 +417,11 @@ export class Checker extends ParseTreeWalker {
this._validateEnumMembers(classTypeResult.classType, node);
// Viper 扩展:`enum X:` 枚举成员的编译期推取值需要能存入成员声明的类型。
if (node.d.isViperEnum) {
this._validateViperEnumMemberValues(node);
}
if (ClassType.isTypedDictClass(classTypeResult.classType)) {
this._validateTypedDictClassSuite(node.d.suite);
}
@@ -1234,6 +1245,9 @@ export class Checker extends ParseTreeWalker {
override visitAssignment(node: AssignmentNode): boolean {
this._evaluator.evaluateTypesForStatement(node);
// Viper 扩展:整数字面量必须能存入声明类型的位宽(如 int 为 i32)。
this._validateViperIntegerLiteralType(node);
if (node.d.annotationComment) {
this._evaluator.getType(node.d.annotationComment);
@@ -2018,6 +2032,12 @@ export class Checker extends ParseTreeWalker {
return;
}
// Viper 扩展:`enum X:` 的成员可以只写成员名(裸名 / 逗号分隔的裸名),
// 它们是成员声明而不是"无效果的表达式",不报未使用表达式。
if (this._isViperEnumMemberNameExpression(node)) {
return;
}
const simpleExpressionTypes = [
ParseNodeType.UnaryOperation,
ParseNodeType.BinaryOperation,
@@ -2060,6 +2080,144 @@ export class Checker extends ParseTreeWalker {
}
}
// Viper 扩展:判断表达式语句是否为 `enum X:` 的成员名声明
// (类体内单独的裸名语句,或逗号分隔的裸名元组)。
private _isViperEnumMemberNameExpression(node: ParseNode): boolean {
if (node.nodeType === ParseNodeType.Name) {
return this._isViperEnumMemberNameNode(node);
}
if (
node.nodeType === ParseNodeType.Tuple &&
node.d.items.length > 0 &&
node.d.items.every((item) => item.nodeType === ParseNodeType.Name)
) {
// 逗号分隔的裸名成员(enum X: a, b, c)。
return ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true)?.d.isViperEnum === true;
}
return false;
}
// Viper 扩展:判断裸名节点是否直接作为 `enum X:` 类体内的成员声明语句出现。
private _isViperEnumMemberNameNode(node: NameNode): boolean {
const parent = node.parent;
const isMemberStatement =
parent?.nodeType === ParseNodeType.StatementList ||
(parent?.nodeType === ParseNodeType.Tuple && parent.parent?.nodeType === ParseNodeType.StatementList);
return (
isMemberStatement &&
ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true)?.d.isViperEnum === true
);
}
// Viper 扩展:校验 `enum X:` 枚举成员的编译期推导取值能否存入成员声明的类型。
// 取值从 0 递增推导(见 enums.ts),故按非负取值给出范围(i1 即 0 ~ 1)。
// 只处理"仅注解无赋值"的成员:带显式赋值的成员(如 `c: t.i1 = 2`)由
// _validateViperIntegerLiteralType 按整数字面量规则统一校验,避免重复报错。
private _validateViperEnumMemberValues(node: ClassNode) {
const memberValues = getViperEnumMemberValues(node);
node.d.suite.d.statements.forEach((statement) => {
if (statement.nodeType !== ParseNodeType.StatementList) {
return;
}
statement.d.statements.forEach((subStatement) => {
// `成员: 类型`(无显式赋值):取值由编译期推导。
if (
subStatement.nodeType !== ParseNodeType.TypeAnnotation ||
subStatement.d.valueExpr.nodeType !== ParseNodeType.Name
) {
return;
}
const nameNode = subStatement.d.valueExpr;
const value = memberValues.get(nameNode.d.value);
if (value === undefined) {
return;
}
const range = this._getViperIntegerTypeRange(
subStatement.d.annotation,
/* allowNegativeBitVector */ false
);
if (!range || (value >= range.min && value <= range.max)) {
return;
}
this._evaluator.addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
LocMessage.viperEnumValueOutOfRange().format({
value: value.toString(),
type: range.typeName,
min: range.min.toString(),
max: range.max.toString(),
}),
nameNode
);
});
});
}
// Viper 扩展:取枚举成员类型注解对应的整型可表示范围。
// 优先使用注解书写形式(如 `i1` / `t.CInt8T`)解析,别名形式再回退到已解析类型名。
private _getViperIntegerTypeRange(
declaredTypeNode: ExpressionNode,
allowNegativeBitVector: boolean
): ViperIntegerRange | undefined {
const writtenName = getViperWrittenTypeName(declaredTypeNode);
if (writtenName) {
const range = getViperIntegerTypeRangeFromName(writtenName, allowNegativeBitVector);
if (range) {
return range;
}
}
const annotationType = this._evaluator.getTypeOfAnnotation(declaredTypeNode);
if (annotationType && isClass(annotationType) && annotationType.shared.name) {
const range = getViperIntegerTypeRangeFromName(annotationType.shared.name, allowNegativeBitVector);
if (range) {
return range;
}
}
return undefined;
}
// Viper 扩展:`NAME: 类型 = 整数字面量` 时校验字面量能否存入类型的位宽。
// 例如 `define CONST_INT: int = 10000000000000000000000000000000`
// (int 为 i32,无法正确存储该数字,需要扩容);`define` 与普通声明形式同样适用。
private _validateViperIntegerLiteralType(node: AssignmentNode) {
const leftExpr = node.d.leftExpr;
if (leftExpr.nodeType !== ParseNodeType.TypeAnnotation) {
return;
}
const literalValue = getViperIntegerLiteralValue(node.d.rightExpr);
if (literalValue === undefined) {
return;
}
const range = this._getViperIntegerTypeRange(leftExpr.d.annotation, /* allowNegativeBitVector */ true);
if (!range || (literalValue >= range.min && literalValue <= range.max)) {
return;
}
this._evaluator.addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
LocMessage.viperIntegerLiteralOutOfRange().format({
// 用源码书写形式,超长字面量不丢精度(如 10^31)。
value: ParseTreeUtils.printExpression(node.d.rightExpr),
type: range.typeName,
min: range.min.toString(),
max: range.max.toString(),
}),
node.d.rightExpr
);
}
// 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) {
@@ -4508,6 +4666,12 @@ export class Checker extends ParseTreeWalker {
return;
}
// Viper 扩展:`enum X:` 的裸名成员是成员声明(取值由编译期推导),
// 不是在读取一个未绑定的变量。
if (this._isViperEnumMemberNameNode(node)) {
return;
}
// Skip this for keyword argument names.
if (node.parent?.nodeType === ParseNodeType.Argument && node.parent.d.name === node) {
return;
@@ -7883,3 +8047,114 @@ export class Checker extends ParseTreeWalker {
});
}
}
// Viper 扩展:整型的可表示范围(用于整数字面量 / 枚举取值的位宽校验)。
interface ViperIntegerRange {
typeName: string;
min: bigint;
max: bigint;
}
// Viper 扩展:取类型注解的书写形式(如 `i1`、`t.CInt8T` 取 `CInt8T`)。
function getViperWrittenTypeName(declaredTypeNode: ExpressionNode): string | undefined {
if (declaredTypeNode.nodeType === ParseNodeType.Name) {
return declaredTypeNode.d.value;
}
if (declaredTypeNode.nodeType === ParseNodeType.MemberAccess) {
return declaredTypeNode.d.member.d.value;
}
return undefined;
}
function viperSignedRange(typeName: string, bits: number): ViperIntegerRange | undefined {
if (!Number.isInteger(bits) || bits < 1 || bits > 64) {
return undefined;
}
// 目标 ES 版本低于 ES2020,不能用 BigInt 字面量(1n),统一用 BigInt()。
const half = BigInt(1) << BigInt(bits - 1);
return { typeName, min: -half, max: half - BigInt(1) };
}
function viperUnsignedRange(typeName: string, bits: number): ViperIntegerRange | undefined {
if (!Number.isInteger(bits) || bits < 1 || bits > 64) {
return undefined;
}
return { typeName, min: BigInt(0), max: (BigInt(1) << BigInt(bits)) - BigInt(1) };
}
// Viper 扩展:按类型名给出可表示范围。仅覆盖宽度明确的整型,其余返回 undefined
// (不做校验,避免误报)。位宽来源见 wiki 02-type-system.md。
// allowNegativeBitVector:LLVM 位向量短名(iN)是否允许负数——位向量的符号由运算决定,
// 枚举成员取值必然非负(从 0 递增),传 false 时范围即 [0, 2^N-1];
// 一般整数字面量可为负,传 true 时取有/无符号并集 [-2^(N-1), 2^N-1]。
function getViperIntegerTypeRangeFromName(
typeName: string,
allowNegativeBitVector: boolean
): ViperIntegerRange | undefined {
let match = /^u(\d+)$/.exec(typeName);
if (match) {
return viperUnsignedRange(typeName, Number(match[1]));
}
match = /^i(\d+)$/.exec(typeName);
if (match) {
const bits = Number(match[1]);
const unsignedRange = viperUnsignedRange(typeName, bits);
if (!allowNegativeBitVector || !unsignedRange) {
return unsignedRange;
}
const signedRange = viperSignedRange(typeName, bits);
return signedRange ? { typeName, min: signedRange.min, max: unsignedRange.max } : unsignedRange;
}
// 定宽整数:CInt{n}T / CUInt{n}T / CInt{Least|Fast}{n}T / CIntMaxT / CUIntMaxT。
match = /^CIntMaxT$/.exec(typeName);
if (match) {
return viperSignedRange(typeName, 64);
}
match = /^CUIntMaxT$/.exec(typeName);
if (match) {
return viperUnsignedRange(typeName, 64);
}
match = /^CInt(?:Least|Fast)?(\d+)T$/.exec(typeName);
if (match) {
return viperSignedRange(typeName, Number(match[1]));
}
match = /^CUInt(?:Least|Fast)?(\d+)T$/.exec(typeName);
if (match) {
return viperUnsignedRange(typeName, Number(match[1]));
}
// 基本 C 类型与内置类型。[位宽, 是否有符号]
const basicRanges: Record<string, [number, boolean]> = {
CChar: [8, true],
CSignedChar: [8, true],
CUnsignedChar: [8, false],
CShort: [16, true],
CUnsignedShort: [16, false],
CInt: [32, true],
CUnsigned: [32, false],
CUnsignedInt: [32, false],
CLong: [64, true],
CUnsignedLong: [64, false],
CChar8T: [8, false],
CChar16T: [16, false],
CChar32T: [32, false],
bool: [1, false],
CBool: [1, false],
int: [32, true],
};
const basicRange = basicRanges[typeName];
if (basicRange) {
return basicRange[1] ? viperSignedRange(typeName, basicRange[0]) : viperUnsignedRange(typeName, basicRange[0]);
}
return undefined;
}
+118 -1
View File
@@ -10,7 +10,8 @@
import { assert } from '../common/debug';
import { IsViperDialect } from '../common/dialect';
import { PythonVersion, pythonVersion3_13 } from '../common/pythonVersion';
import { ArgCategory, ExpressionNode, NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
import { ArgCategory, ClassNode, ExpressionNode, NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
import { OperatorType } from '../parser/tokenizerTypes';
import { getFileInfo } from './analyzerNodeInfo';
import { VariableDeclaration } from './declaration';
import { getClassFullName, getEnclosingClass, getTypeSourceId } from './parseTreeUtils';
@@ -301,6 +302,118 @@ export function createEnumType(
return classType;
}
// Viper 扩展:按声明顺序推导 `enum X:` 各成员的编译期取值。
// 规则:从 0 递增;中间显式赋值则改用该值并从其 +1 继续;
// 显式赋的不是整数字面量时无法推导,其后成员也不推导(直到再次遇到显式整数字面量)。
// 供枚举成员 hover 显示与取值越界校验共用同一份规则。
export function getViperEnumMemberValues(classNode: ClassNode): Map<string, bigint> {
const values = new Map<string, bigint>();
// 目标 ES 版本低于 ES2020,不能用 BigInt 字面量(0n / 1n),统一用 BigInt()。
let nextValue: bigint | undefined = BigInt(0);
const addMember = (nameNode: NameNode, explicitValueNode: ExpressionNode | undefined) => {
const value = explicitValueNode ? getViperIntegerLiteralValue(explicitValueNode) : nextValue;
nextValue = value === undefined ? undefined : value + BigInt(1);
if (value !== undefined) {
values.set(nameNode.d.value, value);
}
};
classNode.d.suite.d.statements.forEach((statement) => {
if (statement.nodeType !== ParseNodeType.StatementList) {
return;
}
statement.d.statements.forEach((subStatement) => {
switch (subStatement.nodeType) {
case ParseNodeType.Name:
// 裸名成员。
addMember(subStatement, undefined);
break;
case ParseNodeType.Tuple:
// 逗号分隔的裸名成员(enum X: a, b, c)。
subStatement.d.items.forEach((item) => {
if (item.nodeType === ParseNodeType.Name) {
addMember(item, undefined);
}
});
break;
case ParseNodeType.TypeAnnotation:
// `成员: 类型`(无显式赋值)。
if (subStatement.d.valueExpr.nodeType === ParseNodeType.Name) {
addMember(subStatement.d.valueExpr, undefined);
}
break;
case ParseNodeType.Assignment: {
// `成员 = 值` 或 `成员: 类型 = 值`。
const leftExpr = subStatement.d.leftExpr;
if (
leftExpr.nodeType === ParseNodeType.TypeAnnotation &&
leftExpr.d.valueExpr.nodeType === ParseNodeType.Name
) {
addMember(leftExpr.d.valueExpr, subStatement.d.rightExpr);
} else if (leftExpr.nodeType === ParseNodeType.Name) {
addMember(leftExpr, subStatement.d.rightExpr);
}
break;
}
}
});
});
return values;
}
// Viper 扩展:从字面量表达式取整数值(支持一元负号),非整数字面量返回 undefined。
// 用 bigint 返回,超长整数字面量(tokenizer 已按 bigint 保存)不会丢精度。
export function getViperIntegerLiteralValue(node: ExpressionNode): bigint | undefined {
if (node.nodeType === ParseNodeType.Number) {
if (node.d.isImaginary || !node.d.isInteger) {
return undefined;
}
if (typeof node.d.value === 'bigint') {
return node.d.value;
}
return Number.isInteger(node.d.value) ? BigInt(node.d.value) : undefined;
}
if (node.nodeType === ParseNodeType.UnaryOperation && node.d.operator === OperatorType.Subtract) {
const innerValue = getViperIntegerLiteralValue(node.d.expr);
return innerValue === undefined ? undefined : -innerValue;
}
return undefined;
}
// Viper 扩展:判断裸名节点是否为 `enum X:` 的成员声明。
// 形如:
// enum ASTFlag:
// IsAsync —— 类体内单独的裸名语句
// enum X: a, b, c —— 逗号分隔的裸名元组(每个裸名都是成员)
function isViperEnumBareMemberName(nameNode: NameNode): boolean {
const parent = nameNode.parent;
if (!parent) {
return false;
}
if (parent.nodeType === ParseNodeType.StatementList) {
// 类体内单独的裸名语句(`IsAsync`)。
} else if (parent.nodeType === ParseNodeType.Tuple && parent.parent?.nodeType === ParseNodeType.StatementList) {
// 逗号分隔的裸名(`enum X: a, b, c`)。
} else {
return false;
}
// 仅 `enum X:` 语法声明的枚举适用:普通类体内的裸名语句仍是普通表达式。
return getEnclosingClass(nameNode, /* stopAtFunction */ true)?.d.isViperEnum === true;
}
// Performs the "magic" that the Enum metaclass does at runtime when it
// transforms a value into an enum instance. If the specified name isn't
// an enum member, this function returns undefined indicating that the
@@ -399,6 +512,10 @@ export function transformTypeForEnumMember(
) {
valueTypeExprNode = nameNode.parent.parent.d.rightExpr;
}
} else if (IsViperDialect(getFileInfo(nameNode).executionEnvironment.dialect) && isViperEnumBareMemberName(nameNode)) {
// Viper 扩展:`enum X:` 只写成员名(裸名 / 逗号分隔的裸名)时,
// 成员既无注解也无赋值,但仍是枚举成员(取值由编译期推导)。
isMemberOfEnumeration = true;
}
// The spec specifically excludes names that start and end with a single underscore.
@@ -19401,6 +19401,14 @@ export function createTypeEvaluator(
}
}
// Viper 扩展:`enum X:` 语法声明的枚举隐式继承 t.CEnum。
// 标记 EnumClass 使枚举成员访问(如 ASTFlag.IsAsync)返回枚举类型
// 而不是成员的声明类型(与 class X(t.CEnum) 的语义一致);
// 标记 ViperEnumClass 使枚举可隐式转换为 int(等价 CEnum 子类)。
if (node.d.isViperEnum) {
classType.shared.flags |= ClassTypeFlags.EnumClass | ClassTypeFlags.ViperEnumClass;
}
// Make sure we don't have 'object' derive from itself. Infinite
// recursion will result.
if (
@@ -26842,9 +26850,11 @@ export function createTypeEvaluator(
// C 语言中 enum 值可以隐式转换为 int(如 int x = FR_OK)。
// Viper 的 CEnum 子类(如 FRESULT)的成员(如 FRESULT.FR_OK)
// 可以赋值给 int 类型的变量/字段。
// `enum X:` 语法声明的枚举(ViperEnumClass)等价 CEnum 子类,同样适用。
if (
isClassInstance(destType) && ClassType.isBuiltIn(destType, 'int') &&
isClassInstance(srcType) && isCEnumSubclassType(srcType)
isClassInstance(srcType) &&
(isCEnumSubclassType(srcType) || ClassType.isViperEnumClass(srcType))
) {
return true;
}
@@ -670,6 +670,11 @@ export const enum ClassTypeFlags {
// This class is rejected when used as the second argument to
// an isinstance or issubclass call.
IllegalIsinstanceClass = 1 << 24,
// Viper 扩展:由 `enum X:` 语法声明的枚举类(等价 `class X(t.CEnum):`)。
// 与 EnumClass 同时设置。用于把 Viper 枚举视作 CEnum 子类
// (如 enum → int 的隐式转换)。
ViperEnumClass = 1 << 25,
}
export interface DataClassBehaviors {
@@ -1255,6 +1260,11 @@ export namespace ClassType {
return !!(classType.shared.flags & ClassTypeFlags.EnumClass);
}
// Viper 扩展:是否为 `enum X:` 语法声明的枚举类。
export function isViperEnumClass(classType: ClassType) {
return !!(classType.shared.flags & ClassTypeFlags.ViperEnumClass);
}
export function isPropertyClass(classType: ClassType) {
return !!(classType.shared.flags & ClassTypeFlags.PropertyClass);
}
@@ -19,6 +19,7 @@ import {
isUnresolvedAliasDeclaration,
} from '../analyzer/declaration';
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
import { getViperEnumMemberValues } from '../analyzer/enums';
import * as ParseTreeUtils from '../analyzer/parseTreeUtils';
import { SourceMapper } from '../analyzer/sourceMapper';
import { SynthesizedTypeInfo } from '../analyzer/symbol';
@@ -221,6 +222,24 @@ export function addDocumentationResultsPart(
fail(`Unsupported markup type: ${format}`);
}
// Viper 扩展:构造 `enum X:` 枚举成员的 hover 类型文本(枚举类型 + 编译期推导值),
// 例如 `ASTFlag = 0`。非 `enum X:` 的成员返回 undefined,走常规显示逻辑。
function getViperEnumMemberTypeText(declaration: VariableDeclaration | undefined, name: string): string | undefined {
if (!declaration || declaration.node.nodeType !== ParseNodeType.Name) {
return undefined;
}
const classNode = ParseTreeUtils.getEnclosingClass(declaration.node, /* stopAtFunction */ true);
if (!classNode?.d.isViperEnum) {
return undefined;
}
const className = classNode.d.name.d.value;
const value = getViperEnumMemberValues(classNode).get(name);
return value === undefined ? `${name}: ${className}` : `${name}: ${className} = ${value}`;
}
export function getVariableTypeText(
evaluator: TypeEvaluator,
declaration: VariableDeclaration | undefined,
@@ -236,6 +255,13 @@ export function getVariableTypeText(
label = declaration.isConstant || evaluator.isFinalVariableDeclaration(declaration) ? 'constant' : 'variable';
}
// Viper 扩展:`enum X:` 枚举成员显示枚举类型与编译期推导值(如 `ASTFlag = 0`)。
// 裸名成员没有赋值也没有注解,常规路径只会给出 Unbound。
const viperEnumMemberText = getViperEnumMemberTypeText(declaration, name);
if (viperEnumMemberText) {
return `(${label}) ` + viperEnumMemberText;
}
let typeVarName: string | undefined;
if (type.props?.typeAliasInfo && typeNode.nodeType === ParseNodeType.Name) {
@@ -463,6 +463,14 @@ export namespace Localizer {
export const enumMemberSet = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.enumMemberSet'));
export const enumMemberTypeAnnotation = () => getRawString('Diagnostic.enumMemberTypeAnnotation');
export const viperEnumValueOutOfRange = () =>
new ParameterizedString<{ value: string; type: string; min: string; max: string }>(
getRawString('Diagnostic.viperEnumValueOutOfRange')
);
export const viperIntegerLiteralOutOfRange = () =>
new ParameterizedString<{ value: string; type: string; min: string; max: string }>(
getRawString('Diagnostic.viperIntegerLiteralOutOfRange')
);
export const viperBeforeInitReserved = () => getRawString('Diagnostic.viperBeforeInitReserved');
export const exceptionGroupIncompatible = () => getRawString('Diagnostic.exceptionGroupIncompatible');
export const exceptGroupMismatch = () => getRawString('Diagnostic.exceptGroupMismatch');
@@ -354,6 +354,14 @@
"message": "Type annotations are not allowed for enum members",
"comment": "{Locked='enum'}"
},
"viperEnumValueOutOfRange": {
"message": "Enum member value {value} is out of range for type \"{type}\" (representable range is {min} to {max})",
"comment": "{Locked='{value}'}{Locked='{type}'}{Locked='{min}'}{Locked='{max}'}"
},
"viperIntegerLiteralOutOfRange": {
"message": "Type \"{type}\" cannot correctly store the number {value} (representable range is {min} to {max}); widen the type",
"comment": "{Locked='{value}'}{Locked='{type}'}{Locked='{min}'}{Locked='{max}'}"
},
"viperBeforeInitReserved": {
"message": "\"__before_init__\" is auto-generated by the compiler and cannot be defined manually",
"comment": "{Locked='__before_init__'}"
@@ -161,6 +161,8 @@
"enumMemberDelete": "无法删除 Enum 成员 \"{name}\"",
"enumMemberSet": "无法分配 Enum 成员 \"{name}\"",
"enumMemberTypeAnnotation": "enum 成员不允许使用类型批注",
"viperEnumValueOutOfRange": "枚举成员的值 {value} 超出类型 \"{type}\" 的可表示范围({min} ~ {max})",
"viperIntegerLiteralOutOfRange": "类型 \"{type}\" 无法正确存储数字 {value}(可表示范围 {min} ~ {max}),需要扩容",
"viperBeforeInitReserved": "\"__before_init__\" 由编译器自动生成,不允许手动定义",
"exceptGroupMismatch": "Try 语句不能同时包含 \"except\" 和 \"except*\"",
"exceptGroupRequiresType": "异常组语法(\"except*\")需要异常类型",
@@ -568,6 +568,10 @@ export interface ClassNode extends ParseNodeBase<ParseNodeType.Class> {
typeParams: TypeParameterListNode | undefined;
arguments: ArgumentNode[];
suite: SuiteNode;
// Viper 扩展:由 `enum X:` 语法声明(等价 `class X(t.CEnum):`)。
// 成员可以只写成员名(裸名 / 逗号分隔的裸名),值由编译期推导。
isViperEnum: boolean;
};
}
@@ -587,6 +591,7 @@ export namespace ClassNode {
typeParams,
arguments: [],
suite: suite,
isViperEnum: false,
},
};
@@ -650,6 +655,7 @@ export namespace ClassNode {
a: undefined,
d: { statements: [], typeComment: undefined },
},
isViperEnum: false,
},
};
+70 -2
View File
@@ -489,6 +489,14 @@ export class Parser {
return this._parseFunctionDef();
case KeywordType.Define:
// Viper 扩展:`define NAME: 类型 = 值` / `define NAME = 值` 为编译期常量声明
// (值必须能存入声明的类型位宽,见 checker 的整数字面量位宽校验)。
// 与 `define f(...)`(宏函数,等价 def)区分:常量声明的名字后跟 ':' 或 '='。
if (this._isViperDefineConstDeclaration()) {
this._getNextToken();
return this._parseSimpleStatement();
}
// Viper 扩展:define 宏函数(与 def 语法一致,语义为编译期宏展开)。
return this._parseFunctionDef();
@@ -1590,7 +1598,14 @@ export class Parser {
}
// suite: ':' (simple_stmt | NEWLINE INDENT stmt+ DEDENT)
private _parseSuite(isFunction = false, skipBody = false, postColonCallback?: () => void): SuiteNode {
// allowTrailingIndent(Viper 扩展):允许 simple_stmt 之后再接 NEWLINE INDENT stmt+ DEDENT,
// 用于 `enum X: a, b, c` 后换行 + TAB 续写枚举成员。
private _parseSuite(
isFunction = false,
skipBody = false,
postColonCallback?: () => void,
allowTrailingIndent = false
): SuiteNode {
const nextToken = this._peekToken();
const suite = SuiteNode.create(nextToken);
@@ -1734,6 +1749,34 @@ export class Parser {
const simpleStatement = this._parseSimpleStatement();
suite.d.statements.push(simpleStatement);
simpleStatement.parent = suite;
// Viper 扩展:`enum X: a, b, c` 允许换行 + TAB 继续写成员(等效缩进块)。
// 注:_parseSimpleStatement 已吃掉行尾的 NewLine,这里只需看 Indent。
if (allowTrailingIndent && this._peekTokenType() === TokenType.Indent) {
const bodyIndentToken = this._getNextToken() as IndentToken;
if (bodyIndentToken.isIndentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), bodyIndentToken);
}
while (true) {
const nextToken = this._peekToken();
if (nextToken.type === TokenType.Dedent || nextToken.type === TokenType.EndOfStream) {
if (nextToken.type === TokenType.Dedent) {
this._getNextToken();
}
break;
}
const statement = this._parseStatement();
if (!statement) {
// Perform basic error recovery to get to the next line.
this._consumeTokensUntilType([TokenType.NewLine]);
} else {
statement.parent = suite;
suite.d.statements.push(statement);
}
}
}
}
if (suite.d.statements.length > 0) {
@@ -2620,6 +2663,21 @@ export class Parser {
return false;
}
// Viper 扩展:判断 `define` 之后是否为编译期常量声明(`NAME: 类型 = 值` / `NAME = 值`)。
// 位置基于 define 关键字:`_peekToken(1)` 是名字,`_peekToken(2)` 是 ':' 或 '='。
private _isViperDefineConstDeclaration(): boolean {
if (this._peekToken(1).type !== TokenType.Identifier) {
return false;
}
const afterName = this._peekToken(2);
if (afterName.type === TokenType.Colon) {
return true;
}
return afterName.type === TokenType.Operator && (afterName as OperatorToken).operatorType === OperatorType.Assign;
}
// classdef: 'class' NAME ['(' [arglist] ')'] suite
private _parseClassDef(decorators?: DecoratorNode[]): ClassNode {
const classToken = this._getKeywordToken(KeywordType.Class);
@@ -2653,7 +2711,12 @@ export class Parser {
}
}
const suite = this._parseSuite(/* isFunction */ false, this._parseOptions.skipFunctionAndClassBody);
const suite = this._parseSuite(
/* isFunction */ false,
this._parseOptions.skipFunctionAndClassBody,
/* postColonCallback */ undefined,
/* allowTrailingIndent */ classToken.isViperEnum === true
);
const classNode = ClassNode.create(classToken, NameNode.create(nameToken), suite, typeParameters);
classNode.d.arguments = argList;
@@ -2661,6 +2724,11 @@ export class Parser {
arg.parent = classNode;
});
// Viper 扩展:`enum X:` 语法声明的枚举(tokenizer 在 class 关键字 token 上打标记)。
if (classToken.isViperEnum) {
classNode.d.isViperEnum = true;
}
if (decorators) {
classNode.d.decorators = decorators;
if (decorators.length > 0) {
@@ -647,11 +647,14 @@ enum MagicsKind {
// Viper 支持将 C 修饰/声明语义写成语句前缀关键字:
// inline/export/extern def f(...) —— 修饰关键字,必须与 def 搭配
// fn/fun/func/function f(...) —— def 的别名
// enum/rnum class|struct Foo —— 等价 t.CEnum / t.REnum
// enum Foo: / rnum Foo: —— 单独使用(等价 class Foo:)
// enum/rnum class|struct Foo —— 跳过前缀关键字(后续 class 正常解析)
// enum Foo: —— 等价 `class Foo(t.CEnum):`(枚举)
// rnum Foo: / union Foo: —— 等价 class Foo:
// struct Foo: —— 独立的 struct 关键字(等价 class)
// 返回 'skip':跳过该关键字 token(后续 def/class 正常解析,位置与缩进不变);
// 返回 'class':把 enum/rnum/struct 当作 class 关键字;
// 返回 'class':把 rnum/union/struct 当作 class 关键字;
// 返回 'classEnum':把 enum 当作 class 关键字,并标记为 Viper 枚举
// (等价 `class Foo(t.CEnum)`,analyzer 会赋予 CEnum 枚举语义);
// 返回 'def':把 fn/fun/func/function 当作 def 关键字;
// 返回 'viperModifier':inline/export/extern 生成修饰关键字 token,由 parser
// 判断是否与 def 搭配(搭配则跳过并记录,单独使用则报错);
@@ -662,7 +665,7 @@ function getViperStatementKeywordAction(
identifierEnd: number,
name: string,
precededByViperModifier: boolean
): 'skip' | 'class' | 'def' | 'viperModifier' | undefined {
): 'skip' | 'class' | 'classEnum' | 'def' | 'viperModifier' | undefined {
const nextWord = peekViperKeywordWord(text, identifierEnd);
if (name === 'inline' || name === 'export' || name === 'extern') {
// 修饰关键字仅在语句起始处生效,避免误伤 `self.inline` 等成员名。
@@ -694,12 +697,16 @@ function getViperStatementKeywordAction(
if (nextWord === 'class' || nextWord === 'struct') {
return 'skip';
}
// `enum A:` / `rnum A:` / `union A:` 单独使用(等价 class A:)。
return isViperAtStatementStart(text, identifierStart) &&
nextWord.length > 0 &&
isIdentifierStartChar(nextWord.charCodeAt(0))
? 'class'
: undefined;
// `enum A:` / `rnum A:` / `union A:` 单独使用。
if (
!isViperAtStatementStart(text, identifierStart) ||
nextWord.length === 0 ||
!isIdentifierStartChar(nextWord.charCodeAt(0))
) {
return undefined;
}
// `enum A:` 等价 `class A(t.CEnum):`;`rnum A:` / `union A:` 等价 `class A:`。
return name === 'enum' ? 'classEnum' : 'class';
}
if (name === 'struct') {
// 仅当 struct 位于语句起始(行首仅空白)且后跟标识符(类名)时才视为 class,
@@ -1519,6 +1526,13 @@ export class Tokenizer {
this._tokens.push(KeywordToken.create(start, length, KeywordType.Class, this._getComments()));
return true;
}
if (viperAction === 'classEnum') {
// `enum Foo:` → `class Foo(t.CEnum):`:合成带枚举标记的 class 关键字 token。
this._tokens.push(
KeywordToken.create(start, length, KeywordType.Class, this._getComments(), /* isViperEnum */ true)
);
return true;
}
if (viperAction === 'def') {
this._tokens.push(KeywordToken.create(start, length, KeywordType.Def, this._getComments()));
return true;
@@ -343,6 +343,11 @@ export namespace NewLineToken {
export interface KeywordToken extends Token {
readonly type: TokenType.Keyword;
readonly keywordType: KeywordType;
// Viper 扩展:该 class 关键字由 Viper 的 `enum X:` 语法合成
// (等价于 `class X(t.CEnum):`)。parser 据此在 ClassNode 上打标记,
// analyzer 据此赋予枚举语义。
readonly isViperEnum?: boolean;
}
export namespace KeywordToken {
@@ -350,7 +355,8 @@ export namespace KeywordToken {
start: number,
length: number,
keywordType: KeywordType,
comments: Comment[] | undefined
comments: Comment[] | undefined,
isViperEnum = false
): KeywordToken {
if (comments !== undefined) {
return {
@@ -359,6 +365,7 @@ export namespace KeywordToken {
type: TokenType.Keyword,
comments,
keywordType,
isViperEnum,
};
}
@@ -367,6 +374,7 @@ export namespace KeywordToken {
length,
type: TokenType.Keyword,
keywordType,
isViperEnum,
};
}
@@ -0,0 +1,12 @@
import t
define MAX_SIZE: int = 1024
define PAGE_SIZE = 4096
define PAGE_BYTES: t.CUInt64T = 18446744073709551615
define SMALL: t.CInt8T = -128
define TINY: t.i8 = 127
def use() -> int:
a: int = MAX_SIZE
b: int = PAGE_SIZE
return a + b
@@ -0,0 +1,5 @@
define square(x: int) -> int:
return x * x
def use() -> int:
return square(3)
@@ -0,0 +1,11 @@
import t
define BIG_INT: int = 10000000000000000000000000000000
PLAIN_BIG: int = 10000000000000000000000000000000
define SMALL_ENUM: t.CInt8T = 200
define OK_BIG: t.CUInt64T = 18446744073709551615
define OK_NEG: int = -2147483648
define OK_POS: int = 2147483647
def use() -> int:
return 0
@@ -0,0 +1,9 @@
import t
define MAX_SIZE: int = 1024
define FLAG = 1
def use() -> int:
a: int = MAX_SIZE
b: int = FLAG
return a + b
@@ -0,0 +1,19 @@
import t
class CEnumRef(t.CEnum):
RED: t.State = 0
GREEN = 1
enum ASTFlag:
IsAsync
Simple
HasStar: int
enum Mid:
a
b = 5
c: t.u8
def use() -> int:
v: int = ASTFlag.IsAsync
return 0
@@ -0,0 +1,12 @@
import t
enum ASTFlag:
IsAsync
Simple
HasStar: int
def use_enum() -> int:
a: int = ASTFlag.IsAsync
b: int = ASTFlag.Simple
c: int = ASTFlag.HasStar
return 0
@@ -0,0 +1,8 @@
import t
enum X: a, b, c
d
def use_x() -> int:
v: int = X.d
return 0
@@ -0,0 +1,10 @@
import t
enum Small:
a
b
c: t.i1
def use_small() -> int:
v: int = Small.a
return 0
@@ -0,0 +1,10 @@
import t
enum Mid:
a
b = 5
c: t.u8
def use_mid() -> int:
v: int = Mid.c
return 0
@@ -0,0 +1,243 @@
/*
* viperDefineConst.test.ts
*
* 验证 Viper 的 `define` 编译期常量声明与整数字面量位宽校验:
* - `define NAME: 类型 = 值` / `define NAME = 值`(后者由编译器推导类型大小);
* - `define f(...)`(宏函数,等价 def)不受影响;
* - 字面量无法存入声明类型位宽时报错(如 int 为 i32 存不下 10^31),
* 非 define 的普通声明形式同样报错(类型大小无法正确存储数字,需要扩容)。
*
* 运行:npx jest src/tests/viperDefineConst.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 { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { FullAccessHost } from '../common/fullAccessHost';
import { convertOffsetToPosition } from '../common/positionUtils';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FunctionNode, MemberAccessNode } from '../parser/parseNodes';
import { KeywordType } from '../parser/tokenizerTypes';
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
let testFileIndex = 0;
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, '__define_const__');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, `define_const_${testFileIndex++}.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');
const parseResults = sourceFile.getParseResults();
assert(parseResults, 'parse results not found');
return { program, configOptions, parseResults, uri, serviceProvider };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): string[] {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map((diag) => {
const line = diag.range ? diag.range.start.line + 1 : -1;
return `[category=${diag.category}][line=${line}] ${diag.message}`;
});
}
test('define NAME: 类型 = 值 / define NAME = 值 解析为普通声明,值大小可存入类型', () => {
const code = `import t
define MAX_SIZE: int = 1024
define PAGE_SIZE = 4096
define PAGE_BYTES: t.CUInt64T = 18446744073709551615
define SMALL: t.CInt8T = -128
define TINY: t.i8 = 127
def use() -> int:
a: int = MAX_SIZE
b: int = PAGE_SIZE
return a + b
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
// define 常量声明被解析为普通赋值语句(不是函数定义)。
const functions: number[] = [];
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator, 'evaluator not available');
const lines = parseResults.parserOutput.lines;
class Collector extends ParseTreeWalker {
override visitFunction(node: FunctionNode) {
functions.push(convertOffsetToPosition(node.start, lines).line + 1);
return true;
}
}
new Collector().walk(parseResults.parserOutput.parseTree);
assert.deepStrictEqual(functions, [9], `define 常量声明不应被当作函数定义:${JSON.stringify(functions)}`);
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 诊断 ===');
diagnostics.forEach((d) => console.log(` ${d}`));
const bad = diagnostics.filter((d) => /未定义|undefined|not defined|未使用|no effect|unused|无法正确存储/i.test(d));
assert.strictEqual(bad.length, 0, `不应报未定义/未使用/位宽错误:${JSON.stringify(diagnostics, undefined, 2)}`);
program.dispose();
serviceProvider.dispose();
});
test('define 宏函数(define f(...))仍解析为函数定义', () => {
const code = `define square(x: int) -> int:
return x * x
def use() -> int:
return square(3)
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
const lines = parseResults.parserOutput.lines;
const functions: { line: number; isDefineKeyword: boolean }[] = [];
class Collector extends ParseTreeWalker {
override visitFunction(node: FunctionNode) {
functions.push({
line: convertOffsetToPosition(node.start, lines).line + 1,
isDefineKeyword: (node.d.firstToken as any).keywordType === KeywordType.Define,
});
return true;
}
}
new Collector().walk(parseResults.parserOutput.parseTree);
// L1 的 define 宏函数 + L4 的普通 def 都应解析为函数;只有 define 那个带 isDefine 标记。
assert.strictEqual(functions.length, 2, `应解析出 2 个函数:${JSON.stringify(functions)}`);
assert(functions[0].isDefineKeyword, `define 宏函数应保留 define 关键字标记:${JSON.stringify(functions)}`);
assert(!functions[1].isDefineKeyword, `普通 def 不应带 define 标记:${JSON.stringify(functions)}`);
const diagnostics = getDiagnostics(program, configOptions, uri);
const errors = diagnostics.filter((d) => d.includes('category=0') || d.includes('category=1'));
assert.strictEqual(errors.length, 0, `不应有错误:${JSON.stringify(diagnostics, undefined, 2)}`);
program.dispose();
serviceProvider.dispose();
});
test('字面量超出声明类型位宽时报错(define 与普通声明都报)', () => {
const code = `import t
define BIG_INT: int = 10000000000000000000000000000000
PLAIN_BIG: int = 10000000000000000000000000000000
define SMALL_ENUM: t.CInt8T = 200
define OK_BIG: t.CUInt64T = 18446744073709551615
define OK_NEG: int = -2147483648
define OK_POS: int = 2147483647
def use() -> int:
return 0
`;
const { program, configOptions, uri, serviceProvider } = buildProgramForCode(code);
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 诊断 ===');
diagnostics.forEach((d) => console.log(` ${d}`));
const rangeErrors = diagnostics.filter((d) => d.includes('需要扩容'));
const errorLines = rangeErrors.map((d) => d.match(/\[line=(\d+)\]/)?.[1]);
assert.deepStrictEqual(
errorLines.sort(),
['3', '4', '5'],
`应只在 L3(define 超长)/L4(普通声明超长)/L5(CInt8T 存不下 200)报错:${JSON.stringify(
diagnostics,
undefined,
2
)}`
);
assert(rangeErrors.every((d) => d.includes('10000000000000000000000000000000') || d.includes('200')), JSON.stringify(rangeErrors));
program.dispose();
serviceProvider.dispose();
});
test('define 常量与普通常量在引用处类型正确', () => {
const code = `import t
define MAX_SIZE: int = 1024
define FLAG = 1
def use() -> int:
a: int = MAX_SIZE
b: int = FLAG
return a + b
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator, 'evaluator not available');
const lines = parseResults.parserOutput.lines;
const types: { line: number; type: string }[] = [];
class Collector extends ParseTreeWalker {
override visitMemberAccess(node: MemberAccessNode) {
return true;
}
override visitName(node: any) {
if (node.d.value === 'MAX_SIZE' || node.d.value === 'FLAG') {
const type = evaluator!.getType(node);
types.push({
line: convertOffsetToPosition(node.start, lines).line + 1,
type: type ? program.printType(type) : '(undefined)',
});
}
return true;
}
}
new Collector().walk(parseResults.parserOutput.parseTree);
console.log('=== 常量引用类型 ===');
types.forEach((t) => console.log(` L${t.line}: ${t.type}`));
const usageTypes = types.filter((t) => t.line >= 7);
assert.strictEqual(usageTypes.length, 2, `应收集到 2 处引用:${JSON.stringify(types)}`);
usageTypes.forEach((t) => {
assert(!t.type.includes('Unknown') && !t.type.includes('Unbound'), `引用类型不应退化:${JSON.stringify(t)}`);
});
program.dispose();
serviceProvider.dispose();
});
@@ -0,0 +1,311 @@
/*
* viperEnumFlag.test.ts
*
* 验证 Viper 的 `enum X:` 语法(等价 `class X(t.CEnum):`):
* - 成员可以只写成员名(裸名 / 逗号分隔的裸名),不报"未定义变量/未使用表达式";
* - 取值由编译期从 0 递增推导,中间可显式赋值(显式赋值后从该值 + 1 继续);
* - 推导值或显式赋值超出成员声明类型(如 i1)的可表示范围时报错;
* - `enum X: a, b, c` 之后可换行 + TAB 继续写成员。
*
* 运行:npx jest src/tests/viperEnumFlag.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { CancellationToken, MarkupContent, MarkupKind, Position } from 'vscode-languageserver';
import { ImportResolver } from '../analyzer/importResolver';
import { Program } from '../analyzer/program';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { FullAccessHost } from '../common/fullAccessHost';
import { convertOffsetToPosition } from '../common/positionUtils';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { MemberAccessNode } from '../parser/parseNodes';
import { HoverProvider } from '../languageService/hoverProvider';
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
let testFileIndex = 0;
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, '__enum_flag__');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, `enum_flag_${testFileIndex++}.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');
const parseResults = sourceFile.getParseResults();
assert(parseResults, 'parse results not found');
return { program, configOptions, parseResults, uri, serviceProvider };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): string[] {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map((diag) => {
const line = diag.range ? diag.range.start.line + 1 : -1;
return `[category=${diag.category}][line=${line}] ${diag.message}`;
});
}
// 收集文件中所有成员访问表达式(形如 `ASTFlag.IsAsync`)的类型。
function collectMemberAccessTypes(program: Program, parseResults: any, memberNames: string[]) {
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator, 'evaluator not available');
const ev = evaluator;
const lines = parseResults.parserOutput.lines;
const results: { line: number; member: string; type: string }[] = [];
class Collector extends ParseTreeWalker {
override visitMemberAccess(node: MemberAccessNode) {
const memberName = (node.d.member as any)?.d?.value ?? '';
if (memberNames.includes(memberName)) {
const pos = convertOffsetToPosition(node.start, lines);
const type = ev.getType(node as any);
results.push({
line: pos.line + 1,
member: memberName,
type: type ? program.printType(type) : '(undefined)',
});
}
return true;
}
}
new Collector().walk(parseResults.parserOutput.parseTree);
return results;
}
function assertNoUndefinedOrUnused(diagnostics: string[]) {
const bad = diagnostics.filter(
(d) => /未定义|undefined|not defined|未使用|no effect|unused/i.test(d)
);
assert.strictEqual(bad.length, 0, `不应报未定义/未使用:${JSON.stringify(diagnostics, undefined, 2)}`);
}
// 取指定位置的 hover 文本(Markdown)。
function getHoverText(program: Program, uri: any, line: number, character: number): string {
const provider = new HoverProvider(
program as any,
uri,
{ line, character } as Position,
MarkupKind.Markdown,
CancellationToken.None
);
const hover = provider.getHover();
return hover ? (hover.contents as MarkupContent).value : '(无 hover)';
}
test('枚举成员 hover 显示枚举类型与推导值', () => {
const code = `import t
class CEnumRef(t.CEnum):
RED: t.State = 0
GREEN = 1
enum ASTFlag:
IsAsync
Simple
HasStar: int
enum Mid:
a
b = 5
c: t.u8
def use() -> int:
v: int = ASTFlag.IsAsync
return 0
`;
const { program, uri, serviceProvider } = buildProgramForCode(code);
const hovers: Record<string, string> = {
'旧写法 RED: t.State = 0': getHoverText(program, uri, 3, 4),
'旧写法 GREEN = 1': getHoverText(program, uri, 4, 4),
'新写法裸名 IsAsync': getHoverText(program, uri, 7, 4),
'新写法 Simple': getHoverText(program, uri, 8, 4),
'新写法 HasStar: int': getHoverText(program, uri, 9, 4),
'显式赋值 b = 5': getHoverText(program, uri, 13, 4),
'显式赋值 + 类型 c: t.u8': getHoverText(program, uri, 14, 4),
'使用点 ASTFlag.IsAsync': getHoverText(program, uri, 17, 21),
};
Object.entries(hovers).forEach(([key, value]) => {
console.log(`=== ${key} ===`);
console.log(value);
});
// `enum X:` 成员:显示枚举类型 + 编译期推导值(裸名成员不再显示 Unbound)。
assert(hovers['新写法裸名 IsAsync'].includes('IsAsync: ASTFlag = 0'), hovers['新写法裸名 IsAsync']);
assert(hovers['新写法 Simple'].includes('Simple: ASTFlag = 1'), hovers['新写法 Simple']);
assert(hovers['新写法 HasStar: int'].includes('HasStar: ASTFlag = 2'), hovers['新写法 HasStar: int']);
// 显式赋值后从该值 +1 继续(b = 5 ⇒ c = 6)。
assert(hovers['显式赋值 b = 5'].includes('b: Mid = 5'), hovers['显式赋值 b = 5']);
assert(hovers['显式赋值 + 类型 c: t.u8'].includes('c: Mid = 6'), hovers['显式赋值 + 类型 c: t.u8']);
// 使用点同样显示推导值(同一份声明)。
assert(hovers['使用点 ASTFlag.IsAsync'].includes('IsAsync: ASTFlag = 0'), hovers['使用点 ASTFlag.IsAsync']);
// 旧写法(CEnum 子类)保持原样,不受影响。
assert(hovers['旧写法 RED: t.State = 0'].includes('RED: t.State'), hovers['旧写法 RED: t.State = 0']);
program.dispose();
serviceProvider.dispose();
});
test('enum X: 裸名成员被识别为枚举成员,取值从 0 递增', () => {
const code = `import t
enum ASTFlag:
IsAsync
Simple
HasStar: int
def use_enum() -> int:
a: int = ASTFlag.IsAsync
b: int = ASTFlag.Simple
c: int = ASTFlag.HasStar
return 0
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
const memberTypes = collectMemberAccessTypes(program, parseResults, ['IsAsync', 'Simple', 'HasStar']);
console.log('=== 成员访问类型 ===');
memberTypes.forEach((m) => console.log(` L${m.line} .${m.member}: ${m.type}`));
assert.strictEqual(memberTypes.length, 3, `成员访问未全部收集:${JSON.stringify(memberTypes)}`);
memberTypes.forEach((m) => {
assert(m.type.includes('ASTFlag'), `成员 .${m.member} 应解析为 ASTFlag 枚举类型:${m.type}`);
});
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 诊断 ===');
diagnostics.forEach((d) => console.log(` ${d}`));
assertNoUndefinedOrUnused(diagnostics);
const errors = diagnostics.filter((d) => d.includes('category=0') || d.includes('category=1'));
assert.strictEqual(errors.length, 0, `不应有错误:${JSON.stringify(diagnostics, undefined, 2)}`);
program.dispose();
serviceProvider.dispose();
});
test('enum X: a, b, c 同行使 + 换行 TAB 续写', () => {
const code = `import t
enum X: a, b, c
d
def use_x() -> int:
v: int = X.d
return 0
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
const memberTypes = collectMemberAccessTypes(program, parseResults, ['a', 'b', 'c', 'd']);
console.log('=== 成员访问类型 ===');
memberTypes.forEach((m) => console.log(` L${m.line} .${m.member}: ${m.type}`));
assert.strictEqual(memberTypes.length, 1, `应收集到 1 个成员访问:${JSON.stringify(memberTypes)}`);
assert(memberTypes[0].type.includes('X'), `成员 .d 应解析为 X 枚举类型:${memberTypes[0].type}`);
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 诊断 ===');
diagnostics.forEach((d) => console.log(` ${d}`));
assertNoUndefinedOrUnused(diagnostics);
const errors = diagnostics.filter((d) => d.includes('category=0') || d.includes('category=1'));
assert.strictEqual(errors.length, 0, `不应有错误:${JSON.stringify(diagnostics, undefined, 2)}`);
program.dispose();
serviceProvider.dispose();
});
test('enum X: 推导值超出成员声明类型(i1 存不下 2)报错', () => {
const code = `import t
enum Small:
a
b
c: t.i1
def use_small() -> int:
v: int = Small.a
return 0
`;
const { program, configOptions, uri, serviceProvider } = buildProgramForCode(code);
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 诊断 ===');
diagnostics.forEach((d) => console.log(` ${d}`));
assertNoUndefinedOrUnused(diagnostics);
const rangeErrors = diagnostics.filter((d) => d.includes('i1') && d.includes('2'));
assert.strictEqual(
rangeErrors.length,
1,
`应恰好报 1 个 i1 溢出错误:${JSON.stringify(diagnostics, undefined, 2)}`
);
assert(rangeErrors[0].includes('line=6'), `应报在 c 所在行(第 6 行):${rangeErrors[0]}`);
program.dispose();
serviceProvider.dispose();
});
test('enum X: 中间显式赋值后继续递增,取值在范围内不报错', () => {
const code = `import t
enum Mid:
a
b = 5
c: t.u8
def use_mid() -> int:
v: int = Mid.c
return 0
`;
const { program, configOptions, uri, serviceProvider } = buildProgramForCode(code);
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 诊断 ===');
diagnostics.forEach((d) => console.log(` ${d}`));
assertNoUndefinedOrUnused(diagnostics);
const errors = diagnostics.filter((d) => d.includes('category=0') || d.includes('category=1'));
assert.strictEqual(errors.length, 0, `不应有错误:${JSON.stringify(diagnostics, undefined, 2)}`);
program.dispose();
serviceProvider.dispose();
});