feat(vipeye-internal): 支持 export/extern 变量声明(extern 禁止初始值)
- export NAME: T [= v] -> T | t.CExport(裸名定义,不加模块 SHA1 头),允许初始值; extern NAME: T -> T | t.State(= CExtern | CExport 仅声明),不允许初始值: extern a: int = 1 报「extern 变量声明不能带初始值(符号定义在外部)」 - 与函数形态由第三个 token 区分(`:` 变量 / `(` `[` 函数),与 TPC _is_export_extern_var 同判据;export def / export f() 形态逐字不变 - 存储类标记按修饰符处理:Struct|CPtr 折叠与联合下标 skip 列表改用 isCModifierType(避免 extern p: t.CPtr 折成 CPtr[State]、str|State 下标误报); CArray 元素访问快路径放宽到"联合里的 CArray 成员"(gdt[i].access 仍解析) - typePrinter 增加 CExport->export / State->extern 前缀渲染(hover 显示 export int) - 新增 viperExportExternVar.test.ts 4 用例,jest 809 -> 813
This commit is contained in:
1 parent
c8817cacb1
commit
0bc7a7c4d4
10 files changed
+367
-34
No files matched your search
@@ -156,6 +156,7 @@ packages/vipeye-internal/src/tests/__viper_const_ptr_index__/
|
||||
packages/vipeye-internal/src/tests/__viper_const_ptr_iter__/
|
||||
packages/vipeye-internal/src/tests/__viper_variadic_noimport__/
|
||||
packages/vipeye-internal/src/tests/__viper_carray_element__/
|
||||
packages/vipeye-internal/src/tests/__viper_export_extern__/
|
||||
|
||||
# perfCompare.py cached commit bundles
|
||||
/build/perfCompare/
|
||||
|
||||
@@ -8263,8 +8263,11 @@ export function createTypeEvaluator(
|
||||
|
||||
// Viper 扩展:TypeSet 类型组合的下标访问语义。
|
||||
// Viper 中 `|` 表示类型组合(如 CConst | str = const char*),
|
||||
// 对 TypeSet 中的修饰类型(CConst/CStatic/CPtr/CVoid/CDefine/CArray/CAttr)跳过下标访问,
|
||||
// 对 TypeSet 中的修饰类型(isCModifierType:CConst/CStatic/CVolatile/
|
||||
// CExport/State/CExtern/CInline/…/CAttr)与 CPtr/CVoid 跳过下标访问,
|
||||
// 让 combineTypes 只合并非修饰类型的结果(如 str/bytes 的下标返回 int/CChar)。
|
||||
// 注:CArray 不在此列 —— 它有真实的元素访问语义,由本函数开头的
|
||||
// CArray 快路径统一处理(裸类型与联合成员都取元素类型)。
|
||||
const viperIndexFileInfo = AnalyzerNodeInfo.getFileInfo(node);
|
||||
const isViperTypeSetIndex =
|
||||
IsViperDialect(viperIndexFileInfo.executionEnvironment.dialect) &&
|
||||
@@ -8272,14 +8275,7 @@ export function createTypeEvaluator(
|
||||
baseTypeResult.type.priv.subtypes.some(
|
||||
(subtype) =>
|
||||
isClassInstance(subtype) &&
|
||||
(isCConstType(subtype) ||
|
||||
isCStaticType(subtype) ||
|
||||
isCVolatileType(subtype) ||
|
||||
isCPtrType(subtype) ||
|
||||
isCVoidType(subtype) ||
|
||||
isCDefineType(subtype) ||
|
||||
isViperAttrMarkerType(subtype) ||
|
||||
isCArrayType(subtype))
|
||||
(isCModifierType(subtype) || isCPtrType(subtype) || isCVoidType(subtype))
|
||||
);
|
||||
|
||||
// Viper 扩展:PEP 695 风格显式类型实参 `fn[TypeArgs]`。
|
||||
@@ -8301,17 +8297,13 @@ export function createTypeEvaluator(
|
||||
const selfType = isTypeVar(unexpandedSubtype) ? unexpandedSubtype : undefined;
|
||||
|
||||
// Viper TypeSet:修饰类型跳过下标访问,避免产生 Unknown 污染结果。
|
||||
// (CArray 除外:它有元素访问语义,走本函数开头的 CArray 快路径。)
|
||||
if (
|
||||
isViperTypeSetIndex &&
|
||||
isClassInstance(concreteSubtype) &&
|
||||
(isCConstType(concreteSubtype) ||
|
||||
isCStaticType(concreteSubtype) ||
|
||||
isCVolatileType(concreteSubtype) ||
|
||||
(isCModifierType(concreteSubtype) ||
|
||||
isCPtrType(concreteSubtype) ||
|
||||
isCVoidType(concreteSubtype) ||
|
||||
isCDefineType(concreteSubtype) ||
|
||||
isViperAttrMarkerType(concreteSubtype) ||
|
||||
isCArrayType(concreteSubtype))
|
||||
isCVoidType(concreteSubtype))
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -8866,16 +8858,22 @@ export function createTypeEvaluator(
|
||||
// 下标恒为 Unknown,进而 `buf[i].field` 误报「无法解析类型上的成员访问」。
|
||||
// 按 C 语义直接取第一个类型实参(元素类型),与 CPtr 分支读
|
||||
// `baseType.priv.typeArgs[0]` 同法,不依赖 TypeVar 替换。
|
||||
//
|
||||
// 基类型也可以是**联合**(`CArray[T, N] | t.CExport` / `| t.State` ——
|
||||
// 存储类标记只作修饰,见 parser._parseViperExportExternVarStatement):
|
||||
// 此时取联合里的 CArray 成员,避免修饰符成员被当"不可下标"报错。
|
||||
const viperArrayFileInfo = AnalyzerNodeInfo.getFileInfo(node);
|
||||
if (
|
||||
IsViperDialect(viperArrayFileInfo.executionEnvironment.dialect) &&
|
||||
usage.method === 'get' &&
|
||||
isClassInstance(baseType) &&
|
||||
isCArrayType(baseType) &&
|
||||
baseType.priv.typeArgs &&
|
||||
baseType.priv.typeArgs.length > 0
|
||||
usage.method !== 'del'
|
||||
) {
|
||||
return { type: makeTopLevelTypeVarsConcrete(baseType.priv.typeArgs[0]) };
|
||||
const arrayType = isClassInstance(baseType) && isCArrayType(baseType)
|
||||
? baseType
|
||||
: findSubtype(baseType, (subtype) => isClassInstance(subtype) && isCArrayType(subtype));
|
||||
const arrayTypeArgs = (arrayType as ClassType | undefined)?.priv.typeArgs;
|
||||
if (arrayTypeArgs && arrayTypeArgs.length > 0) {
|
||||
return { type: makeTopLevelTypeVarsConcrete(arrayTypeArgs[0]) };
|
||||
}
|
||||
}
|
||||
|
||||
// Viper 扩展:CPtr[T] 指针下标解引用语义。
|
||||
|
||||
@@ -726,8 +726,11 @@ function printTypeInternal(
|
||||
// Viper 用联合(TypeSet)表示「类型 + 描述符」,如 `CConst | CPtr[int]`(const int*)、
|
||||
// `CVolatile | int`(volatile int)。展示时须把它们当作**前缀限定符**,
|
||||
// 而不是联合成员,否则会暴露出内部表示("CConst | rawptr[int]")。
|
||||
// 仅覆盖有唯一 C 关键字的修饰符;State / CExport / CPass / CDefine
|
||||
// (声明标记、编译期元指令)另有语义,保持原样。
|
||||
// `CExport`(`export x: T`)/ `State`(`extern x: T` = CExtern | CExport)是本
|
||||
// 语言软关键字 `export` / `extern` 声明变量的脱糖产物(与 TPC
|
||||
// `_parse_export_extern_var` 同形),同属存储类修饰,故与 static/register 等一样
|
||||
// 按前缀渲染(`export int` / `extern int`)。
|
||||
// 其余声明标记 / 编译期元指令(CPass / CDefine)保持原样。
|
||||
function getViperModifierKeyword(type: Type): string | undefined {
|
||||
if (!isClass(type)) {
|
||||
return undefined;
|
||||
@@ -748,6 +751,10 @@ function getViperModifierKeyword(type: Type): string | undefined {
|
||||
return 'inline';
|
||||
case 'CExtern':
|
||||
return 'extern';
|
||||
case 'CExport':
|
||||
return 'export';
|
||||
case 'State':
|
||||
return 'extern';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -4223,12 +4223,12 @@ export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): T
|
||||
isClass(subtype) &&
|
||||
// None 是空指针取值而非结构体,不能作为 CPtr 的类型参数(否则 None | CPtr[T] 会误折叠成 CPtr[None])
|
||||
!ClassType.isBuiltIn(subtype, 'NoneType') &&
|
||||
// 修饰符(CConst/CStatic/CVolatile/CExport/State/CExtern/CInline/CDefine/CAttr 等)
|
||||
// 只作修饰,不能当被指向类型:`t.CPtr | t.State`(extern 变量的存储类标记)保持联合,
|
||||
// 否则会折叠成 CPtr[State] 这种把修饰符当结构体的错误类型。
|
||||
!isCModifierType(subtype) &&
|
||||
!isCVoidType(subtype) &&
|
||||
!isCArrayType(subtype) &&
|
||||
!isCConstType(subtype) &&
|
||||
!isCStaticType(subtype) &&
|
||||
!isCVolatileType(subtype) &&
|
||||
!isCDefineType(subtype) &&
|
||||
!ClassType.isBuiltIn(subtype, 'object')
|
||||
) {
|
||||
structSubtypes.push(subtype);
|
||||
|
||||
@@ -1212,6 +1212,8 @@ export namespace Localizer {
|
||||
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperStaticUsedOutsideOfModule'));
|
||||
export const viperConstDerefAssignment = () =>
|
||||
new ParameterizedString<{ type: string }>(getRawString('Diagnostic.viperConstDerefAssignment'));
|
||||
export const viperExternVarCannotHaveValue = () =>
|
||||
getRawString('Diagnostic.viperExternVarCannotHaveValue');
|
||||
export const viperLenNoLenMethod = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperLenNoLenMethod'));
|
||||
export const unhashableDictKey = () => getRawString('Diagnostic.unhashableDictKey');
|
||||
|
||||
@@ -1639,6 +1639,7 @@
|
||||
"viperExternBodyInvalid": "The extern function \"{name}\" must have a body of only 'pass' or 'None'",
|
||||
"viperStaticUsedOutsideOfModule": "\"{name}\" is a static symbol and cannot be used across modules (static is visible only inside its defining module)",
|
||||
"viperConstDerefAssignment": "Cannot assign through a dereference of a const pointer (the data pointed to by \"{type}\" is read-only)",
|
||||
"viperExternVarCannotHaveValue": "An extern variable declaration cannot have an initializer (the symbol is defined externally)",
|
||||
"viperLenNoLenMethod": "Type \"{name}\" has no __len__ method and cannot be used with len()",
|
||||
"unhashableDictKey": "Dictionary key must be hashable",
|
||||
"unhashableSetEntry": {
|
||||
|
||||
@@ -631,6 +631,7 @@
|
||||
"viperExternBodyInvalid": "extern 函数 \"{name}\" 的函数体必须为 pass 或 None",
|
||||
"viperStaticUsedOutsideOfModule": "\"{name}\" 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
|
||||
"viperConstDerefAssignment": "不能给 const 指针的解引用赋值(\"{type}\" 指向的数据为只读)",
|
||||
"viperExternVarCannotHaveValue": "extern 变量声明不能带初始值(符号定义在外部)",
|
||||
"viperLenNoLenMethod": "类型 \"{name}\" 没有 __len__ 方法,不能用于 len()",
|
||||
"unhashableDictKey": "字典密钥必须可哈希",
|
||||
"unhashableSetEntry": "Set 条目必须是可哈希的",
|
||||
|
||||
@@ -530,6 +530,13 @@ export class Parser {
|
||||
(modifierText === 'extern' || modifierText === 'export') &&
|
||||
this._peekToken(1).type === TokenType.Identifier
|
||||
) {
|
||||
// Viper 扩展:`export NAME: T [= v]` / `extern NAME: T` 变量声明。
|
||||
// 与 `extern A(...)` / `export A(...)` 函数声明由第三个 token 区分:
|
||||
// `:` 是变量(类型注解),`(` / `[` 是函数(参数表 / 泛型)。
|
||||
// (与 TPC includes/ast/parser.vp `_is_export_extern_var` 同判据)
|
||||
if (this._peekToken(2).type === TokenType.Colon) {
|
||||
return this._parseViperExportExternVarStatement(modifierToken, modifierText);
|
||||
}
|
||||
// extern/export 直接声明形式:`extern A(...)`。
|
||||
// 消费修饰关键字后,下一个标识符即函数名;defToken 用修饰关键字
|
||||
// 作为范围起点(diagnostic 定位)。
|
||||
@@ -5434,7 +5441,7 @@ export class Parser {
|
||||
this._viperStaticDescriptorInAnnotation = true;
|
||||
|
||||
return BinaryOperationNode.create(
|
||||
this._createViperStaticMarker(staticToken.start),
|
||||
this._createViperStorageClassMarker(staticToken.start, 'CStatic'),
|
||||
innerType,
|
||||
this._createViperSyntheticBarToken(staticToken),
|
||||
OperatorType.BitwiseOr
|
||||
@@ -5575,11 +5582,13 @@ export class Parser {
|
||||
return nameNode;
|
||||
}
|
||||
|
||||
// Viper 扩展:合成 `t.CStatic` 属性标记(模块根是隐式名,不要求源码 import t)。
|
||||
private _createViperStaticMarker(start: number): ExpressionNode {
|
||||
// Viper 扩展:合成 `t.<memberName>` 存储类标记(模块根是隐式名,不要求源码
|
||||
// import t)。`CStatic`(static 变量)、`CExport`(export 变量)、
|
||||
// `State`(extern 变量 = CExtern | CExport)共用本函数。
|
||||
private _createViperStorageClassMarker(start: number, memberName: string): ExpressionNode {
|
||||
return MemberAccessNode.create(
|
||||
this._createViperImplicitModuleName(start, 't'),
|
||||
NameNode.create(IdentifierToken.create(start, 0, 'CStatic', /* comments */ undefined))
|
||||
NameNode.create(IdentifierToken.create(start, 0, memberName, /* comments */ undefined))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5626,7 +5635,7 @@ export class Parser {
|
||||
// 故锚点取内层注解自身的起点,零长度合成名正好落在注解内侧。
|
||||
const anchorStart = annotationNode.d.annotation.start;
|
||||
const wrappedAnnotation = BinaryOperationNode.create(
|
||||
this._createViperStaticMarker(anchorStart),
|
||||
this._createViperStorageClassMarker(anchorStart, 'CStatic'),
|
||||
annotationNode.d.annotation,
|
||||
OperatorToken.create(anchorStart, 0, OperatorType.BitwiseOr, /* comments */ undefined),
|
||||
OperatorType.BitwiseOr
|
||||
@@ -5637,6 +5646,67 @@ export class Parser {
|
||||
return statementList;
|
||||
}
|
||||
|
||||
// Viper 扩展:`export NAME: T [= v]` / `extern NAME: T` 模块级变量声明。
|
||||
// 与 TPC includes/ast/parser.vp `_parse_export_extern_var` 逐条对应:
|
||||
// export → 注解改写为 `T | t.CExport`(定义端裸名,**不加模块 SHA1 头**
|
||||
// —— 内联汇编 / C 符号对接必需),允许带初始值;
|
||||
// extern → 注解改写为 `T | t.State`(= t.CExtern | t.CExport:仅声明 + 裸名,
|
||||
// 与 `extern def` 同口径),**不允许初始值**(符号定义在外部),
|
||||
// `extern a: int = 1` 报错(响亮失败,非静默)。
|
||||
// `t` 用隐式模块根(不要求源码 import t)。
|
||||
// 存储类标记只作修饰:TypeSet 语义下不改变数据类型(isCModifierType 统一跳过),
|
||||
// 故 `gdt[i].field` / `gdt = ...` 等访问与普通变量一致。
|
||||
private _parseViperExportExternVarStatement(modifierToken: KeywordToken, modifierText: string): StatementNode {
|
||||
this._getNextToken(); // 消费 export / extern
|
||||
const statementList = this._parseSimpleStatement();
|
||||
const isExtern = modifierText === 'extern';
|
||||
|
||||
let annotationNode: TypeAnnotationNode | undefined;
|
||||
let hasInitialValue = false;
|
||||
for (const inner of statementList.d.statements) {
|
||||
if (inner.nodeType === ParseNodeType.TypeAnnotation) {
|
||||
annotationNode = inner;
|
||||
break;
|
||||
}
|
||||
if (inner.nodeType === ParseNodeType.Assignment) {
|
||||
hasInitialValue = true;
|
||||
const leftExpr = inner.d.leftExpr;
|
||||
if (leftExpr.nodeType === ParseNodeType.TypeAnnotation) {
|
||||
annotationNode = leftExpr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!annotationNode) {
|
||||
// 调用方已保证 `export`/`extern` 后是 `名字 :`;走到这里说明写法异常。
|
||||
this._addSyntaxError(LocMessage.viperModifierRequiresDef().format({ name: modifierText }), modifierToken);
|
||||
return statementList;
|
||||
}
|
||||
|
||||
if (isExtern && hasInitialValue) {
|
||||
this._addSyntaxError(LocMessage.viperExternVarCannotHaveValue(), modifierToken);
|
||||
}
|
||||
|
||||
// 合成的存储类标记贴在注解**右侧**(`T | t.CExport` / `T | t.State`,
|
||||
// 与 TPC `BinOp(ann, BitOr, storage_attr)` 同形)。锚点取内层注解的**末尾**:
|
||||
// 标记零长度、落在注解范围内侧,既不会让子节点跑到父节点范围之外
|
||||
// (绑定期 testWalker 的 "Child node ... is not contained within its parent"),
|
||||
// 也保持「子节点按位置递增」。
|
||||
const annotationEnd = annotationNode.d.annotation.start + annotationNode.d.annotation.length;
|
||||
const annotation = annotationNode.d.annotation;
|
||||
const markerAnnotation = BinaryOperationNode.create(
|
||||
annotation,
|
||||
this._createViperStorageClassMarker(annotationEnd, isExtern ? 'State' : 'CExport'),
|
||||
OperatorToken.create(annotationEnd, 0, OperatorType.BitwiseOr, /* comments */ undefined),
|
||||
OperatorType.BitwiseOr
|
||||
);
|
||||
markerAnnotation.parent = annotationNode;
|
||||
annotationNode.d.annotation = markerAnnotation;
|
||||
|
||||
return statementList;
|
||||
}
|
||||
|
||||
// Viper 扩展:把 `@__attribute__(...)` 装饰器脱糖为
|
||||
// `@c.Attribute(t.attr.a, t.attr.b)`——与 TPC/TPV 的
|
||||
// includes/ast/parser.vp `_DesugarMagicAttribute` 逐字段同形,因此 class 与 def
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* viperExportExternVar.test.ts
|
||||
*
|
||||
* 验证 Viper 的 `export` / `extern` **变量**声明(仅 LSP 侧实现,TPC/TPV 不动):
|
||||
* ① `export NAME: T [= v]` —— 注解脱糖为 `T | t.CExport`(与 TPC
|
||||
* includes/ast/parser.vp `_parse_export_extern_var` 同形:定义端**裸名**,
|
||||
* 不加模块 SHA1 头),**允许**初始值;
|
||||
* ② `extern NAME: T` —— 注解脱糖为 `T | t.State`(= t.CExtern | t.CExport:
|
||||
* 仅声明 + 裸名),**不允许**初始值:`extern a: int = 1` 报错;
|
||||
* ③ 与函数形态(`export def f()` / `export f(...)`)由第三个 token 区分:
|
||||
* `:` 是变量,`(` / `[` 是函数;函数形态逐字不变。
|
||||
* ④ 存储类标记只作修饰:`export gdt: [7]gdt_entry = []` 的 `gdt[i].field`
|
||||
* 仍能解析出元素类型(`[]` 零初始化不报类型错误),
|
||||
* 展示为前缀限定符(`export CArray[gdt_entry, 7]` / `extern int`)。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperExportExternVar.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { IndexNode, NameNode, ParseNodeType, TypeAnnotationNode } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
function buildProgram(code: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__viper_export_extern__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
fs.mkdirSync(testRoot, { recursive: true });
|
||||
const filePath = path.join(testRoot, 'main.vp');
|
||||
fs.writeFileSync(filePath, code, 'utf8');
|
||||
|
||||
const projectRoot = UriEx.file(testRoot);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[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 uri = UriEx.file(filePath);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
return { program, configOptions, uri, serviceProvider, testRoot };
|
||||
}
|
||||
|
||||
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any) {
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
assert(sourceFile, 'source file not found');
|
||||
return (sourceFile.getDiagnostics(configOptions) || []).map((diag: any) => ({
|
||||
category: diag.category,
|
||||
line: diag.range ? diag.range.start.line + 1 : -1,
|
||||
message: diag.message as string,
|
||||
}));
|
||||
}
|
||||
|
||||
/** 按名字收集模块级变量的展示类型(printType 走 typePrinter,与 hover 一致)。 */
|
||||
function collectVarTypes(program: Program, uri: any): Map<string, string> {
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const parseResults = program.getParseResults(uri);
|
||||
assert(parseResults, 'parse results not found');
|
||||
const evaluator = program.evaluator!;
|
||||
const result = new Map<string, string>();
|
||||
|
||||
class Collector extends ParseTreeWalker {
|
||||
override visitName(node: NameNode): boolean {
|
||||
const parent = node.parent;
|
||||
if (parent?.nodeType === ParseNodeType.TypeAnnotation && (parent as TypeAnnotationNode).d.valueExpr === node) {
|
||||
const type = evaluator.getType(node);
|
||||
if (type && !result.has(node.d.value)) {
|
||||
result.set(node.d.value, program.printType(type));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
override visitIndex(node: IndexNode): boolean {
|
||||
const type = evaluator.getType(node);
|
||||
const text = sourceFile!.getParseResults()!.text.substr(node.start, node.length);
|
||||
if (text.startsWith('gdt[') || text.startsWith('ext_str[')) {
|
||||
result.set(text, type ? program.printType(type) : '(undefined)');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new Collector().walk(parseResults.parserOutput.parseTree);
|
||||
return result;
|
||||
}
|
||||
|
||||
test('export 变量声明:允许初始值,元素访问/展示均正常', () => {
|
||||
const code = `import t
|
||||
|
||||
class gdt_entry:
|
||||
limit_low: uint16
|
||||
base_low: uint16
|
||||
base_mid: uint8
|
||||
access: uint8
|
||||
flags: uint8
|
||||
base_high: uint8
|
||||
|
||||
export gdt: [7]gdt_entry = []
|
||||
export tss_buf: [104]uint8
|
||||
export counter: int = 3
|
||||
|
||||
def touch() -> uint8:
|
||||
gdt[1].access = 0x92
|
||||
return gdt[0].access
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testRoot } = buildProgram(code);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== export 变量诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const real = diagnostics.filter((d) => d.category === 0 || d.category === 1);
|
||||
assert.strictEqual(real.length, 0, `export 变量声明不应报错:${JSON.stringify(real)}`);
|
||||
|
||||
const types = collectVarTypes(program, uri);
|
||||
console.log('=== 变量类型 ===');
|
||||
types.forEach((v, k) => console.log(` ${k}: ${v}`));
|
||||
|
||||
// 存储类标记按前缀限定符展示(与 static int 同一机制)
|
||||
assert.strictEqual(types.get('counter'), 'export int', `counter 应展示为 export int`);
|
||||
assert(
|
||||
types.get('gdt') === 'export CArray[gdt_entry, 7]' ||
|
||||
types.get('gdt')?.startsWith('export CArray[gdt_entry'),
|
||||
`gdt 应展示为 export CArray[gdt_entry, 7],实际: ${types.get('gdt')}`
|
||||
);
|
||||
// 元素访问解析出元素类型(CArray 快路径,联合形态也生效)
|
||||
assert.strictEqual(types.get('gdt[0]'), 'gdt_entry', `gdt[0] 应为 gdt_entry`);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('extern 变量声明:不带初始值合法,展示为 extern 前缀', () => {
|
||||
const code = `import t
|
||||
|
||||
extern ext_flag: int
|
||||
extern ext_str: str
|
||||
extern ext_ptr: t.CPtr
|
||||
|
||||
def touch() -> char:
|
||||
return ext_str[0]
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testRoot } = buildProgram(code);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== extern 变量诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const real = diagnostics.filter((d) => d.category === 0 || d.category === 1);
|
||||
assert.strictEqual(real.length, 0, `extern 变量声明(不带初始值)不应报错:${JSON.stringify(real)}`);
|
||||
|
||||
const types = collectVarTypes(program, uri);
|
||||
console.log('=== 变量类型 ===');
|
||||
types.forEach((v, k) => console.log(` ${k}: ${v}`));
|
||||
|
||||
assert.strictEqual(types.get('ext_flag'), 'extern int', `ext_flag 应展示为 extern int`);
|
||||
assert.strictEqual(types.get('ext_str'), 'extern str', `ext_str 应展示为 extern str`);
|
||||
// `extern ext_ptr: t.CPtr`:CPtr 是裸指针模板,不能被修饰符折叠成 CPtr[State]
|
||||
assert(
|
||||
types.get('ext_ptr')?.startsWith('extern rawptr'),
|
||||
`ext_ptr 应展示为 extern rawptr[...],实际: ${types.get('ext_ptr')}`
|
||||
);
|
||||
assert.strictEqual(types.get('ext_str[0]'), 'char', `ext_str[0] 应为 char`);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('extern 变量声明带初始值报错(符号定义在外部)', () => {
|
||||
const { program, configOptions, uri, serviceProvider, testRoot } = buildProgram('extern a: int = 1\n');
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== extern 带初始值诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const target = diagnostics.filter((d) => d.message.includes('extern 变量声明不能带初始值'));
|
||||
assert.strictEqual(
|
||||
target.length,
|
||||
1,
|
||||
`extern a: int = 1 应报 1 条「不能带初始值」:${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
assert.strictEqual(target[0].category, 0, `应为 error:${JSON.stringify(target[0])}`);
|
||||
assert.strictEqual(target[0].line, 1, `报错应落在第 1 行:${JSON.stringify(target[0])}`);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('export/extern 函数形态不受影响(第三个 token 为 ( / [)', () => {
|
||||
const code = `export def published(x: int) -> int:
|
||||
return x
|
||||
|
||||
extern def external_fn(x: int) -> int
|
||||
|
||||
export direct_decl(x: int) -> int
|
||||
|
||||
def touch() -> int:
|
||||
return published(1) + external_fn(2) + direct_decl(3)
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testRoot } = buildProgram(code);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== export/extern 函数形态诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const real = diagnostics.filter((d) => d.category === 0 || d.category === 1);
|
||||
assert.strictEqual(real.length, 0, `export/extern 函数声明不应报错:${JSON.stringify(real)}`);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -36,7 +36,8 @@
|
||||
- C type descriptors are **skipped** when checking iterability, so a `const` pointer can be iterated: `for ch in path:` with `path: const str` (and `const t.CPtr`) no longer reports "“CConst”不可迭代 / 未定义“__iter__”方法". The predicate now recognizes every Viper pointer (CPtr, `str`, `bytes`) rather than only `CPtr`, which is why `const str` had been missed — `CConst` fell through to the "not iterable" path
|
||||
- The `static` modifier is also accepted **inside a type annotation**: `buf_head: static int = 0` — the spelling actually used throughout ViperOS (`VKernel/Kernel/drivers/**`). It desugars to the very same `t.CStatic | T` and marks the declaration as a static symbol, so it renders as `static int` and the cross-module rule applies identically; previously the `static` inside the annotation was read as an ordinary name and reported `"static" is not defined`. `static` stays a **soft** keyword in this position too — it is consumed only when a type atom follows, so `static` as a variable name (`static: int = 0`, `static = 1`) is untouched. It is now highlighted as a modifier inside annotations as well
|
||||
- `*args` is bound to the `va_list` **pointer** even when the file doesn't `import t`: the Viper var-arg rule used to look up `t.CPtr` through the source-visible `t` module only, so in a file like ViperOS `drivers/serial/uart/serial.vp` (no `import t`) it fell back to Python's `tuple` semantics and `viperlib.vsnprintf(&buf, 1024, fmt, args)` was reported as "无法将"tuple"类型的参数分配给函数"vsnprintf"中类型为"str"的参数"ap"". The lookup now falls back to the compiler built-in `rawptr` from `builtins.vpi` (= `t.CPtr`), the same mechanism `T*` annotations and `[SIZE]T` already use
|
||||
- Indexing a C array now yields the **element type**, so member access on an element resolves: with `kbd_buf: [KBD_BUF_SIZE]usb_kbd_event`, `kbd_buf[i].modifier` used to report "无法解析类型上的成员"modifier"访问,对象可能未定义" and the member name got no semantic token at all (plain white). The CArray stub declares `class CArray(CType, Generic[T, N])` with `def __getitem__(self, index: int) -> T`, but Viper-side `TypeVar(...)` does not produce substitutable type variables (the class's type parameters end up as synthesized `__P0` / `__P1`), so the return type never became concrete. The Viper index path now reads the **first type argument** directly (C semantics: `arr[i]` has the element type), the same way the `CPtr[T]` branch reads `priv.typeArgs[0]`; this covers both `[SIZE]T` and the explicit `t.CArray[T, N]` spelling
|
||||
- Indexing a C array now yields the **element type**, so member access on an element resolves: with `kbd_buf: [KBD_BUF_SIZE]usb_kbd_event`, `kbd_buf[i].modifier` used to report "无法解析类型上的成员"modifier"访问,对象可能未定义" and the member name got no semantic token at all (plain white). The CArray stub declares `class CArray(CType, Generic[T, N])` with `def __getitem__(self, index: int) -> T`, but Viper-side `TypeVar(...)` does not produce substitutable type variables (the class's type parameters end up as synthesized `__P0` / `__P1`), so the return type never became concrete. The Viper index path now reads the **first type argument** directly (C semantics: `arr[i]` has the element type), the same way the `CPtr[T]` branch reads `priv.typeArgs[0]`; this covers both `[SIZE]T` and the explicit `t.CArray[T, N]` spelling, and works for a union base too (`CArray[T, N] | t.CExport`)
|
||||
- **`export` / `extern` on variable declarations** are now supported: `export gdt: [7]gdt_entry = []`, `export tss_buf: [104]uint8` and `extern ext_flag: int`. The variable form is told apart from the function form (`export def f()` / `export f(...)`) by the third token — `:` is a variable (type annotation), `(` / `[` is a function, the same criterion as TransPyC's `_is_export_extern_var`). They desugar exactly like TransPyC's `_parse_export_extern_var`: `export NAME: T [= v]` becomes `T | t.CExport` (bare-named definition, **no module SHA1 header**), `extern NAME: T` becomes `T | t.State` (= `CExtern | CExport`, declaration only). An **initializer is allowed for `export` but rejected for `extern`** — `extern a: int = 1` reports "extern 变量声明不能带初始值(符号定义在外部)", matching the compiler's fail-fast. Storage-class markers render as a prefix qualifier (`export CArray[gdt_entry, 7]`, `extern int`), and the marker is treated as a modifier everywhere else: in the `Struct | CPtr` fold (so `extern p: t.CPtr` stays a pointer instead of degrading to `CPtr[State]`), in the union index access, and as a CArray element access (so `gdt[i].access` still resolves to `uint8` and the zero-initializer `= []` is accepted)
|
||||
|
||||
## 1.1.425
|
||||
|
||||
|
||||
Reference in new issue
Block a user