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:
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;
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
Reference in new issue
Block a user