更新了一些事项,feat(vipeye-internal): builtins.vpi 声明编译器内置 typedef rowptr = t.CPtr;feat(vipeye-internal): None 返回函数仅允许裸 return/return None,return 值为 None 的表达式报错;fix(vipeye-internal): builtins.vp 发现扩展到文件祖先目录与 includes 子目录,修复 uint64 等 typedef 未定义
Validation / Typecheck (push) Has been cancelled
Validation / Style (push) Has been cancelled
Validation / Test macos-latest (push) Has been cancelled
Validation / Test ubuntu-latest (push) Has been cancelled
Validation / Test windows-latest (push) Has been cancelled
Validation / Build (push) Has been cancelled
Validation / Required (push) Has been cancelled
Validation / Typecheck (push) Has been cancelled
Validation / Style (push) Has been cancelled
Validation / Test macos-latest (push) Has been cancelled
Validation / Test ubuntu-latest (push) Has been cancelled
Validation / Test windows-latest (push) Has been cancelled
Validation / Build (push) Has been cancelled
Validation / Required (push) Has been cancelled
This commit is contained in:
1 parent
23cc75e444
commit
aef18c2891
54 files changed
+4554
-1033
No files matched your search
@@ -1,10 +1,74 @@
|
||||
import w32.win32console as w32cmd
|
||||
import stdio
|
||||
import t, c
|
||||
|
||||
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
pagecode: t.CDefine = 65001
|
||||
|
||||
def main() -> int:
|
||||
stdio.printf("Hello, World!")
|
||||
return 0
|
||||
|
||||
class TestStruct():
|
||||
a: t.CInt
|
||||
b: t.CInt
|
||||
|
||||
|
||||
class Obj():
|
||||
val: t.CInt
|
||||
|
||||
|
||||
export def main() -> int:
|
||||
w32cmd.SetConsoleOutputCP(pagecode)
|
||||
w32cmd.SetConsoleCP(pagecode)
|
||||
print("=== Unary * / & (Deref/AddrOf) Test ===")
|
||||
|
||||
# --- Test 1: 标量 & 取地址 + * 解引用 ---
|
||||
x: int = 42
|
||||
px: int | t.CPtr = &x
|
||||
d: int = *px
|
||||
print(f"T1: x={x} *(&x)={d} (expect 42, 42)")
|
||||
if d == 42:
|
||||
print("T1 OK")
|
||||
else:
|
||||
print("T1 FAIL")
|
||||
|
||||
# --- Test 2: 通过指针写回(c.DerefAs 兼容形式) ---
|
||||
c.DerefAs(px, 100)
|
||||
print(f"T2: after c.DerefAs(px, 100) x={x} (expect 100)")
|
||||
if x == 100:
|
||||
print("T2 OK")
|
||||
else:
|
||||
print("T2 FAIL")
|
||||
|
||||
# --- Test 3: 数组元素地址 &arr[i] + * 解引用 ---
|
||||
a: t.CArray[t.CInt, 4]
|
||||
a[0] = 7
|
||||
pa: t.CInt | t.CPtr = &a[0]
|
||||
v3: int = *pa
|
||||
print(f"T3: a[0]={a[0]} *&a[0]={v3} (expect 7, 7)")
|
||||
if v3 == 7:
|
||||
print("T3 OK")
|
||||
else:
|
||||
print("T3 FAIL")
|
||||
|
||||
# --- Test 4: 结构体字段地址 &s.f + * 解引用 ---
|
||||
s: TestStruct
|
||||
s.a = 10
|
||||
pf: t.CInt | t.CPtr = &s.a
|
||||
v4: int = *pf
|
||||
print(f"T4: s.a={s.a} *&s.a={v4} (expect 10, 10)")
|
||||
if v4 == 10:
|
||||
print("T4 OK")
|
||||
else:
|
||||
print("T4 FAIL")
|
||||
|
||||
# --- Test 5: &obj 对象指针(对象变量即指针) ---
|
||||
o: Obj
|
||||
o.val = 99
|
||||
po: Obj | t.CPtr = &o
|
||||
print(f"T5: o.val={o.val} &o->val={po.val} (expect 99, 99)")
|
||||
if po.val == 99:
|
||||
print("T5 OK")
|
||||
else:
|
||||
print("T5 FAIL")
|
||||
|
||||
print("=== Unary * / & Test Done ===")
|
||||
return 0
|
||||
@@ -432,10 +432,25 @@ export class Binder extends ParseTreeWalker {
|
||||
// 仅当导入的最终目标是目录(而非具体文件)时才标黄。
|
||||
const lastResolvedUri = importResult.resolvedUris[importResult.resolvedUris.length - 1];
|
||||
const isFinalTargetDirectory = !lastResolvedUri || lastResolvedUri.isEmpty();
|
||||
// Viper 扩展:`from . import X` 的请求符号(X)能在目录中经隐式导入解析
|
||||
//(存在同名 X.vp 文件)时,说明目录作为包已可正常使用,不再报"命名空间包不完整"。
|
||||
const importFromNode =
|
||||
node.parent?.nodeType === ParseNodeType.ImportFrom ? (node.parent as ImportFromNode) : undefined;
|
||||
const allSymbolsResolvable =
|
||||
importFromNode !== undefined &&
|
||||
importFromNode.d.imports.length > 0 &&
|
||||
importFromNode.d.imports.every((importSymbolNode) => {
|
||||
const importedName = importSymbolNode.d.name.d.value;
|
||||
return (
|
||||
importResult.implicitImports?.has(importedName) ||
|
||||
importResult.filteredImplicitImports?.has(importedName)
|
||||
);
|
||||
});
|
||||
if (
|
||||
importResult.isImportFound &&
|
||||
importResult.isNamespacePackage &&
|
||||
isFinalTargetDirectory &&
|
||||
!allSymbolsResolvable &&
|
||||
IsViperDialect(this._fileInfo.executionEnvironment.dialect)
|
||||
) {
|
||||
this._fileInfo.diagnosticSink.addDiagnosticWithTextRange(
|
||||
@@ -1319,7 +1334,11 @@ export class Binder extends ParseTreeWalker {
|
||||
const postForLabel = this._createBranchLabel();
|
||||
|
||||
// Determine if this loop is guaranteed to execute at least once
|
||||
const isGuaranteedToExecute = this._isNonEmptyListOrTupleLiteral(node.d.iterableExpr);
|
||||
// Viper 方言(C 风格):for 循环变量在循环后仍视为已定义(保留迭代类型),
|
||||
// 不产生 Unbound 分支——与 Python 语义(循环可能零次执行 → Unbound)不同。
|
||||
const isGuaranteedToExecute = IsViperDialect(this._fileInfo.executionEnvironment.dialect)
|
||||
? true
|
||||
: this._isNonEmptyListOrTupleLiteral(node.d.iterableExpr);
|
||||
|
||||
this._addAntecedent(preForLabel, this._currentFlowNode!);
|
||||
this._currentFlowNode = preForLabel;
|
||||
|
||||
@@ -238,6 +238,13 @@ function isViperExternAllowedBody(stmt: ParseNode): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isViperNoneLiteral(expr: ParseNode | undefined): boolean {
|
||||
if (!expr || expr.nodeType !== ParseNodeType.Constant) {
|
||||
return false;
|
||||
}
|
||||
return (expr as ConstantNode).d.constType === KeywordType.None;
|
||||
}
|
||||
|
||||
export class Checker extends ParseTreeWalker {
|
||||
private readonly _moduleNode: ModuleNode;
|
||||
private readonly _fileInfo: AnalyzerFileInfo;
|
||||
@@ -1085,6 +1092,24 @@ export class Checker extends ParseTreeWalker {
|
||||
returnTypeResult.expectedTypeDiagAddendum?.getEffectiveTextRange()
|
||||
);
|
||||
}
|
||||
|
||||
// Viper 扩展:返回类型为 None(无返回值)的函数,return 只能写成
|
||||
// 裸 return 或 return None 字面量。若 return 的是另一个返回 None
|
||||
// 的表达式(如 `return g()`,g 的调用结果在 C 语义中无值),必须报错。
|
||||
if (
|
||||
returnTypeMatches &&
|
||||
node.d.expr &&
|
||||
IsViperDialect(this._fileInfo.executionEnvironment.dialect) &&
|
||||
isNoneInstance(adjReturnType) &&
|
||||
isNoneInstance(returnType) &&
|
||||
!isViperNoneLiteral(node.d.expr)
|
||||
) {
|
||||
this._evaluator.addDiagnostic(
|
||||
DiagnosticRule.reportReturnType,
|
||||
LocMessage.viperReturnExprInNoneFunction(),
|
||||
node.d.expr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import { ConsoleInterface } from '../common/console';
|
||||
import { assert, fail } from '../common/debug';
|
||||
import { IsViperDialect } from '../common/dialect';
|
||||
import { convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { ArgCategory, ExpressionNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
|
||||
import { getFileInfo, getImportInfo } from './analyzerNodeInfo';
|
||||
@@ -50,6 +51,7 @@ import {
|
||||
FunctionType,
|
||||
isClass,
|
||||
isClassInstance,
|
||||
isCTypeSubclassType,
|
||||
isFunction,
|
||||
isFunctionOrOverloaded,
|
||||
isInstantiableClass,
|
||||
@@ -74,6 +76,7 @@ import {
|
||||
derivesFromStdlibClass,
|
||||
doForEachSubtype,
|
||||
isIncompleteUnknown,
|
||||
isNoneInstance,
|
||||
isTypeAliasPlaceholder,
|
||||
mapSubtypes,
|
||||
} from './typeUtils';
|
||||
@@ -1729,6 +1732,7 @@ export function getCodeFlowEngine(
|
||||
function isCallNoReturn(evaluator: TypeEvaluator, flowNode: FlowCall) {
|
||||
const node = flowNode.node;
|
||||
const fileInfo = getFileInfo(node);
|
||||
const isViperDialect = IsViperDialect(fileInfo.executionEnvironment?.dialect);
|
||||
|
||||
// Assume that calls within a pyi file are not "NoReturn" calls.
|
||||
if (fileInfo.isStubFile) {
|
||||
@@ -1803,7 +1807,7 @@ export function getCodeFlowEngine(
|
||||
|
||||
const isCallAwaited = node.parent?.nodeType === ParseNodeType.Await;
|
||||
if (isFunction(callSubtype)) {
|
||||
if (isFunctionNoReturn(callSubtype, isCallAwaited)) {
|
||||
if (isFunctionNoReturn(evaluator, callSubtype, isCallAwaited, isViperDialect)) {
|
||||
noReturnTypeCount++;
|
||||
}
|
||||
} else if (isOverloaded(callSubtype)) {
|
||||
@@ -1813,7 +1817,7 @@ export function getCodeFlowEngine(
|
||||
OverloadedType.getOverloads(callSubtype).forEach((overload) => {
|
||||
overloadCount++;
|
||||
|
||||
if (isFunctionNoReturn(overload, isCallAwaited)) {
|
||||
if (isFunctionNoReturn(evaluator, overload, isCallAwaited, isViperDialect)) {
|
||||
noReturnOverloadCount++;
|
||||
}
|
||||
});
|
||||
@@ -1859,9 +1863,45 @@ export function getCodeFlowEngine(
|
||||
}
|
||||
}
|
||||
|
||||
function isFunctionNoReturn(functionType: FunctionType, isCallAwaited: boolean) {
|
||||
function isFunctionNoReturn(
|
||||
evaluator: TypeEvaluator,
|
||||
functionType: FunctionType,
|
||||
isCallAwaited: boolean,
|
||||
isViperDialect: boolean
|
||||
) {
|
||||
const returnType = FunctionType.getEffectiveReturnType(functionType, /* includeInferred */ false);
|
||||
if (returnType) {
|
||||
if (isViperDialect) {
|
||||
// Viper 方言:函数声明返回 t.State(C 风格 noreturn 标记,仅声明不定义,
|
||||
// 如 sys.exit / stdlib.exit 的 `-> t.State`)视为 NoReturn,调用后流程终止。
|
||||
let hasStateReturn = false;
|
||||
doForEachSubtype(returnType, (retSubtype) => {
|
||||
if (
|
||||
isClass(retSubtype) &&
|
||||
isCTypeSubclassType(retSubtype) &&
|
||||
retSubtype.shared.name === 'State'
|
||||
) {
|
||||
hasStateReturn = true;
|
||||
}
|
||||
});
|
||||
if (hasStateReturn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Viper 无返回注解的函数被隐式声明为 `-> None`(C 语义 void),
|
||||
// 掩盖了实际不返回的 fail-fast 函数(如函数体以 sys.exit(1) 结尾)。
|
||||
// 惰性推断(inferredReturnType)可能尚未就绪,直接通过 flow 分析
|
||||
// 函数体判定是否从不返回(isAfterNodeReachable 主动触发 flow)。
|
||||
if (isNoneInstance(returnType)) {
|
||||
const inferred = functionType.shared.inferredReturnType?.type;
|
||||
if (inferred && isNever(inferred)) {
|
||||
return true;
|
||||
}
|
||||
if (!isAfterNodeReachable(evaluator, functionType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
isClassInstance(returnType) &&
|
||||
ClassType.isBuiltIn(returnType, ['Coroutine', 'CoroutineType']) &&
|
||||
|
||||
@@ -258,32 +258,62 @@ function validateNewAndInitMethods(
|
||||
const overloadsUsedForCall: FunctionType[] = [];
|
||||
let newMethodReturnType: Type | undefined;
|
||||
|
||||
// Validate __new__ if it is present.
|
||||
// Viper 扩展:`__new__` 作为分配器时(返回裸指针或实例|指针联合,如
|
||||
// stmts.vp 中 FunctionDef 的 `__new__` 返回 `FunctionDef | t.CPtr`),其参数
|
||||
// 不参与构造调用校验——Viper 中 `__new__` 由编译器按分配逻辑调用,构造调用
|
||||
// 的用户参数只与 `__init__` 签名匹配(否则 7 参 `__init__` 配 6 参 `__new__`
|
||||
// 会误报 "Expected 6 positional arguments")。此时仅取 `__new__` 的返回类型
|
||||
// 用于后续 `__init__` 绑定判断。
|
||||
let isViperNewAllocator = false;
|
||||
if (newMethodTypeResult) {
|
||||
// Use speculative mode for arg expressions because we don't know whether
|
||||
// we'll need to re-evaluate these expressions later for __init__.
|
||||
const newCallResult = validateNewMethod(
|
||||
evaluator,
|
||||
errorNode,
|
||||
argList,
|
||||
type,
|
||||
skipUnknownArgCheck,
|
||||
inferenceContext,
|
||||
newMethodTypeResult,
|
||||
/* useSpeculativeModeForArgs */ true
|
||||
);
|
||||
if (isFunctionOrOverloaded(newMethodTypeResult.type)) {
|
||||
doForEachSignature(newMethodTypeResult.type, (signature) => {
|
||||
const allocNewReturnType = FunctionType.getEffectiveReturnType(signature);
|
||||
if (allocNewReturnType) {
|
||||
doForEachSubtype(allocNewReturnType, (subtype) => {
|
||||
if (isCPtrType(subtype)) {
|
||||
isViperNewAllocator = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (newCallResult.argumentErrors) {
|
||||
argumentErrors = true;
|
||||
if (isViperNewAllocator) {
|
||||
// 跳过 __new__ 参数校验,直接取其返回类型。
|
||||
if (isFunctionOrOverloaded(newMethodTypeResult.type)) {
|
||||
doForEachSignature(newMethodTypeResult.type, (signature) => {
|
||||
if (!newMethodReturnType) {
|
||||
newMethodReturnType = FunctionType.getEffectiveReturnType(signature);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
appendArray(overloadsUsedForCall, newCallResult.overloadsUsedForCall ?? []);
|
||||
}
|
||||
// Use speculative mode for arg expressions because we don't know whether
|
||||
// we'll need to re-evaluate these expressions later for __init__.
|
||||
const newCallResult = validateNewMethod(
|
||||
evaluator,
|
||||
errorNode,
|
||||
argList,
|
||||
type,
|
||||
skipUnknownArgCheck,
|
||||
inferenceContext,
|
||||
newMethodTypeResult,
|
||||
/* useSpeculativeModeForArgs */ true
|
||||
);
|
||||
|
||||
if (newCallResult.isTypeIncomplete) {
|
||||
isTypeIncomplete = true;
|
||||
}
|
||||
if (newCallResult.argumentErrors) {
|
||||
argumentErrors = true;
|
||||
} else {
|
||||
appendArray(overloadsUsedForCall, newCallResult.overloadsUsedForCall ?? []);
|
||||
}
|
||||
|
||||
newMethodReturnType = newCallResult.returnType;
|
||||
if (newCallResult.isTypeIncomplete) {
|
||||
isTypeIncomplete = true;
|
||||
}
|
||||
|
||||
newMethodReturnType = newCallResult.returnType;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -325,16 +355,33 @@ function validateNewAndInitMethods(
|
||||
}
|
||||
|
||||
// Validate __init__ if it's present.
|
||||
//
|
||||
// Viper 扩展:`__new__` 返回联合类型(如 `FunctionDef | t.CPtr`,实例或裸
|
||||
// 指针)时,`isClassInstance` 对整个联合返回 false,导致 `__init__` 校验被
|
||||
// 跳过,构造调用只按 `__new__` 签名校验(误报:FunctionDef 的 6 参 `__new__`
|
||||
// 报 "Expected 6 positional arguments",而 `__init__` 实际有 7 参含
|
||||
// type_params)。Viper 中 `__new__` 只负责分配内存,`__init__` 仍会被调用,
|
||||
// 因此联合返回只要含 CPtr(Viper 分配语义)就继续校验 `__init__`。
|
||||
let newMethodReturnTypeIsInstance = isClassInstance(newMethodReturnType);
|
||||
if (!newMethodReturnTypeIsInstance) {
|
||||
doForEachSubtype(newMethodReturnType, (subtype) => {
|
||||
if (isCPtrType(subtype)) {
|
||||
newMethodReturnTypeIsInstance = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!isNever(newMethodReturnType) &&
|
||||
!shouldSkipInitEvaluation(evaluator, type, newMethodReturnType) &&
|
||||
isClassInstance(newMethodReturnType)
|
||||
newMethodReturnTypeIsInstance
|
||||
) {
|
||||
// If the __new__ method returned the same type as the class it's constructing
|
||||
// but didn't supply solved type arguments, we'll ignore its specialized return
|
||||
// type and rely on the __init__ method to supply the type arguments instead.
|
||||
let initMethodBindToType = newMethodReturnType;
|
||||
if (
|
||||
isClassInstance(initMethodBindToType) &&
|
||||
initMethodBindToType.priv.typeArgs &&
|
||||
initMethodBindToType.priv.typeArgs.some((typeArg) => isUnknown(typeArg))
|
||||
) {
|
||||
@@ -354,6 +401,12 @@ function validateNewAndInitMethods(
|
||||
initMethodBindToType = ClassType.cloneAsInstance(type);
|
||||
}
|
||||
|
||||
// 纯类实例时保持原类型,联合/CPtr 时已替换为类实例;
|
||||
// 保证传给 getBoundInitMethod / validateInitMethod 的一定是 ClassType。
|
||||
if (!isClassInstance(initMethodBindToType)) {
|
||||
initMethodBindToType = ClassType.cloneAsInstance(type);
|
||||
}
|
||||
|
||||
// Determine whether the class overrides the object.__init__ method.
|
||||
const initMethodDiag = new DiagnosticAddendum();
|
||||
initMethodTypeResult = getBoundInitMethod(evaluator, errorNode, initMethodBindToType, initMethodDiag);
|
||||
@@ -389,7 +442,7 @@ function validateNewAndInitMethods(
|
||||
}
|
||||
}
|
||||
|
||||
if (!validatedArgExpressions && newMethodTypeResult) {
|
||||
if (!validatedArgExpressions && newMethodTypeResult && !isViperNewAllocator) {
|
||||
// If we skipped the __init__ method and the __new__ method was evaluated only
|
||||
// speculatively, evaluate it non-speculatively now so we can report errors.
|
||||
if (!evaluator.isSpeculativeModeInUse(errorNode)) {
|
||||
@@ -409,6 +462,13 @@ function validateNewAndInitMethods(
|
||||
returnType = newMethodReturnType;
|
||||
}
|
||||
|
||||
// Viper 扩展:分配器 `__new__` 且无 `__init__` 时,参数不做构造校验,
|
||||
// 返回类型沿用 `__new__` 的返回类型(实例|指针联合或裸指针)。
|
||||
if (!validatedArgExpressions && isViperNewAllocator) {
|
||||
validatedArgExpressions = true;
|
||||
returnType = newMethodReturnType;
|
||||
}
|
||||
|
||||
// If the class doesn't override object.__new__ or object.__init__, use the
|
||||
// fallback constructor type evaluation for the `object` class.
|
||||
if (!newMethodTypeResult && !initMethodTypeResult) {
|
||||
|
||||
@@ -82,10 +82,11 @@ export function createImportedModuleDescriptor(moduleName: string): ImportedModu
|
||||
type CachedImportResults = Map<string, ImportResult>;
|
||||
|
||||
const supportedNativeLibExtensions = ['.pyd', '.so', '.dylib'];
|
||||
// Viper 模式下同时支持 .vp/.vpi(主)和 .py/.pyi(兼容回退)。
|
||||
// Viper 模式下同时支持 .vp/.vpi/.vpnci(主)和 .py/.pyi(兼容回退)。
|
||||
// .vpi 为可编译注解,.vpnci 为不可编译注解(配置与 .vpi 一致)。
|
||||
// .py/.pyi 支持用于 TransPyC 源码(TPC/TPV 编译器源码仍是 .py 扩展名),
|
||||
// 以及用户未迁移到 .vp 的旧代码。
|
||||
export const supportedSourceFileExtensions = ['.vp', '.vpi', '.py', '.pyi'];
|
||||
export const supportedSourceFileExtensions = ['.vp', '.vpi', '.vpnci', '.py', '.pyi'];
|
||||
export const supportedFileExtensions = [...supportedSourceFileExtensions, ...supportedNativeLibExtensions];
|
||||
|
||||
// Should we allow partial resolution for third-party packages? Some use tricks
|
||||
@@ -223,7 +224,11 @@ export class ImportResolver {
|
||||
result.nonStubImportResult.resolvedUris.length - 1
|
||||
];
|
||||
|
||||
if (nonEmptyUri.hasExtension('.vp') || nonEmptyUri.hasExtension('.vpi')) {
|
||||
if (
|
||||
nonEmptyUri.hasExtension('.vp') ||
|
||||
nonEmptyUri.hasExtension('.vpi') ||
|
||||
nonEmptyUri.hasExtension('.vpnci')
|
||||
) {
|
||||
// We allow pyi in case there are multiple pyi for a compiled module such as
|
||||
// numpy.random.mtrand
|
||||
sourceFileUris.push(nonEmptyUri);
|
||||
@@ -821,7 +826,13 @@ export class ImportResolver {
|
||||
let strippedFileName: string;
|
||||
let isNativeLib = false;
|
||||
|
||||
if (fileExt === '.vp' || fileExt === '.vpi' || fileExt === '.py' || fileExt === '.pyi') {
|
||||
if (
|
||||
fileExt === '.vp' ||
|
||||
fileExt === '.vpi' ||
|
||||
fileExt === '.vpnci' ||
|
||||
fileExt === '.py' ||
|
||||
fileExt === '.pyi'
|
||||
) {
|
||||
strippedFileName = stripFileExtension(filePath.fileName);
|
||||
} else if (
|
||||
_isNativeModuleFileExtension(fileExt) &&
|
||||
@@ -1402,6 +1413,8 @@ export class ImportResolver {
|
||||
// Viper 模式下同时支持 .vp/.vpi(主)和 .py/.pyi(兼容回退)。
|
||||
const pyFilePath = dirPath.initPyUri;
|
||||
const pyiFilePath = dirPath.initPyiUri;
|
||||
// .vpnci 不可编译注解(与 .vpi 同级)
|
||||
const vpnciInitFilePath = dirPath.combinePathsUnsafe('__init__.vpnci');
|
||||
// .py/.pyi 兼容回退路径
|
||||
const compatPyFilePath = dirPath.combinePathsUnsafe('__init__.py');
|
||||
const compatPyiFilePath = dirPath.combinePathsUnsafe('__init__.pyi');
|
||||
@@ -1414,6 +1427,13 @@ export class ImportResolver {
|
||||
isStubFile = true;
|
||||
}
|
||||
isInitFilePresent = true;
|
||||
} else if (allowPyi && this.fileExistsCached(vpnciInitFilePath)) {
|
||||
importLogger?.log(`Resolved import with file '${vpnciInitFilePath}'`);
|
||||
resolvedPaths.push(vpnciInitFilePath);
|
||||
if (isLastPart) {
|
||||
isStubFile = true;
|
||||
}
|
||||
isInitFilePresent = true;
|
||||
} else if (this.fileExistsCached(pyFilePath)) {
|
||||
importLogger?.log(`Resolved import with file '${pyFilePath}'`);
|
||||
resolvedPaths.push(pyFilePath);
|
||||
@@ -1440,6 +1460,7 @@ export class ImportResolver {
|
||||
if (dirName) {
|
||||
const sameNameVpPath = dirPath.combinePathsUnsafe(dirName).addExtension('.vp');
|
||||
const sameNameVpiPath = dirPath.combinePathsUnsafe(dirName).addExtension('.vpi');
|
||||
const sameNameVpnciPath = dirPath.combinePathsUnsafe(dirName).addExtension('.vpnci');
|
||||
if (allowPyi && this.fileExistsCached(sameNameVpiPath)) {
|
||||
importLogger?.log(`Resolved import with Viper same-name file '${sameNameVpiPath}'`);
|
||||
resolvedPaths.push(sameNameVpiPath);
|
||||
@@ -1447,6 +1468,13 @@ export class ImportResolver {
|
||||
isStubFile = true;
|
||||
}
|
||||
isInitFilePresent = true;
|
||||
} else if (allowPyi && this.fileExistsCached(sameNameVpnciPath)) {
|
||||
importLogger?.log(`Resolved import with Viper same-name file '${sameNameVpnciPath}'`);
|
||||
resolvedPaths.push(sameNameVpnciPath);
|
||||
if (isLastPart) {
|
||||
isStubFile = true;
|
||||
}
|
||||
isInitFilePresent = true;
|
||||
} else if (this.fileExistsCached(sameNameVpPath)) {
|
||||
importLogger?.log(`Resolved import with Viper same-name file '${sameNameVpPath}'`);
|
||||
resolvedPaths.push(sameNameVpPath);
|
||||
@@ -1469,6 +1497,7 @@ export class ImportResolver {
|
||||
implicitImports = this.findImplicitImports(moduleDescriptor.nameParts.join('.'), dirPath, [
|
||||
pyFilePath,
|
||||
pyiFilePath,
|
||||
vpnciInitFilePath,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
@@ -1481,6 +1510,8 @@ export class ImportResolver {
|
||||
// 用于 TransPyC 源码(仍是 .py 扩展名)。
|
||||
const pyFilePath = dirPath.packageUri;
|
||||
const pyiFilePath = dirPath.packageStubUri;
|
||||
// .vpnci 不可编译注解(与 .vpi 同级)
|
||||
const vpnciFilePath = dirPath.addExtension('.vpnci');
|
||||
// .py/.pyi 兼容回退路径
|
||||
const compatPyFilePath = dirPath.addExtension('.py');
|
||||
const compatPyiFilePath = dirPath.addExtension('.pyi');
|
||||
@@ -1492,6 +1523,12 @@ export class ImportResolver {
|
||||
if (isLastPart) {
|
||||
isStubFile = true;
|
||||
}
|
||||
} else if (allowPyi && this.fileExistsCached(vpnciFilePath)) {
|
||||
importLogger?.log(`Resolved import with file '${vpnciFilePath}'`);
|
||||
resolvedPaths.push(vpnciFilePath);
|
||||
if (isLastPart) {
|
||||
isStubFile = true;
|
||||
}
|
||||
} else if (this.fileExistsCached(pyFilePath)) {
|
||||
importLogger?.log(`Resolved import with file '${pyFilePath}'`);
|
||||
resolvedPaths.push(pyFilePath);
|
||||
@@ -2117,7 +2154,11 @@ export class ImportResolver {
|
||||
|
||||
if (!entry.name.startsWith('_')) {
|
||||
const packageInit = dirRoot.combinePaths('__init__.vpi');
|
||||
if (this._fileSystemCache.existsSync(packageInit)) {
|
||||
const packageInitVpnci = dirRoot.combinePaths('__init__.vpnci');
|
||||
if (
|
||||
this._fileSystemCache.existsSync(packageInit) ||
|
||||
this._fileSystemCache.existsSync(packageInitVpnci)
|
||||
) {
|
||||
addDirectoryStdlibModule(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { EvalFlags, MagicMethodDeprecationInfo, TypeEvaluator, TypeResult } from
|
||||
import {
|
||||
InferenceContext,
|
||||
convertToInstantiable,
|
||||
convertToInstance,
|
||||
getLiteralTypeClassName,
|
||||
getTypeCondition,
|
||||
getUnionSubtypeCount,
|
||||
@@ -50,6 +51,7 @@ import {
|
||||
} from './typeUtils';
|
||||
import {
|
||||
ClassType,
|
||||
ModuleType,
|
||||
NeverType,
|
||||
Type,
|
||||
TypeBase,
|
||||
@@ -67,6 +69,7 @@ import {
|
||||
isFunctionOrOverloaded,
|
||||
isViperIntegerType,
|
||||
isInstantiableClass,
|
||||
isModule,
|
||||
isNever,
|
||||
isUnion,
|
||||
} from './types';
|
||||
@@ -578,12 +581,11 @@ export function getTypeOfAugmentedAssignment(
|
||||
// Viper 扩展:str/bytes 指针增强赋值(C 指针算术)。
|
||||
// Viper 中 str/bytes 是 i8* 指针,`dest += 1`(指针递增)是合法的
|
||||
// C 指针算术,结果类型保持 str/bytes。Python 中 str 不可变故无此语义。
|
||||
// 仅当运算符为 + 且右操作数是整数时生效。
|
||||
// 仅当运算符为 + 且右操作数是整数(含 C 整数类型)时生效。
|
||||
if (IsViperDialect(getFileInfo(node).executionEnvironment.dialect)) {
|
||||
const leftIsStrBytes = isClassInstance(leftSubtypeUnexpanded) &&
|
||||
ClassType.isBuiltIn(leftSubtypeUnexpanded, ['str', 'bytes']);
|
||||
const rightIsInt = isClassInstance(rightSubtypeUnexpanded) &&
|
||||
ClassType.isBuiltIn(rightSubtypeUnexpanded, 'int');
|
||||
const rightIsInt = isViperIntegerType(rightSubtypeUnexpanded);
|
||||
if (leftIsStrBytes && rightIsInt) {
|
||||
return leftSubtypeUnexpanded;
|
||||
}
|
||||
@@ -689,6 +691,19 @@ export function getTypeOfUnaryOperation(
|
||||
flags: EvalFlags,
|
||||
inferenceContext: InferenceContext | undefined
|
||||
): TypeResult {
|
||||
const isViperDialect = IsViperDialect(getFileInfo(node).executionEnvironment.dialect);
|
||||
|
||||
// Viper 扩展:类型注解上下文中的一元 '*' 表示 C 指针类型(T* = CPtr[T])。
|
||||
// parser 将 `T*`/`T**`/`T***` 后缀解析为 UnaryOperationNode(Multiply) 嵌套,
|
||||
// 此处须在 TypeExpression 守卫(unaryOperationNotAllowed)之前处理。
|
||||
if (
|
||||
isViperDialect &&
|
||||
node.d.operator === OperatorType.Multiply &&
|
||||
(flags & EvalFlags.TypeExpression) !== 0
|
||||
) {
|
||||
return getTypeOfViperPointerAnnotation(evaluator, node);
|
||||
}
|
||||
|
||||
if ((flags & EvalFlags.TypeExpression) !== 0) {
|
||||
evaluator.addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.unaryOperationNotAllowed(), node);
|
||||
return { type: UnknownType.create() };
|
||||
@@ -703,6 +718,19 @@ export function getTypeOfUnaryOperation(
|
||||
return { type: NeverType.createNever(), isIncomplete };
|
||||
}
|
||||
|
||||
// Viper 扩展:一元 '*'(解引用)与 '&'(取地址),与 C 语义一致。
|
||||
// 在 Viper 方言下,前缀 '*'/'&' 被 parser 解析为 UnaryOperation
|
||||
// (operator = Multiply / BitwiseAnd),此处直接按 C 语义求值,
|
||||
// 不走魔法方法路径(指针没有 __pos__ 等)。
|
||||
if (isViperDialect) {
|
||||
if (node.d.operator === OperatorType.Multiply) {
|
||||
return getTypeOfViperDeref(evaluator, node, exprType, isIncomplete);
|
||||
}
|
||||
if (node.d.operator === OperatorType.BitwiseAnd) {
|
||||
return getTypeOfViperAddrOf(evaluator, node, exprType, isIncomplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Map unary operators to magic functions. Note that the bitwise
|
||||
// invert has two magic functions that are aliases of each other.
|
||||
const unaryOperatorMap: { [operator: number]: string } = {
|
||||
@@ -820,6 +848,159 @@ export function getTypeOfUnaryOperation(
|
||||
return { type, isIncomplete, magicMethodDeprecationInfo: deprecatedInfo };
|
||||
}
|
||||
|
||||
// Viper 扩展:一元 '*'(解引用)类型求值,与 C 语义一致。
|
||||
// - CPtr[X](含 `X | t.CPtr` 化简形式)→ 返回目标类型 X(类对象转为实例)
|
||||
// - Unknown/Any → 透传
|
||||
// - 非指针类型 → 报错(C 语义:invalid type argument of unary '*')
|
||||
function getTypeOfViperDeref(
|
||||
evaluator: TypeEvaluator,
|
||||
node: UnaryOperationNode,
|
||||
exprType: Type,
|
||||
isIncomplete: boolean | undefined
|
||||
): TypeResult {
|
||||
const resultTypes: Type[] = [];
|
||||
let sawNonPointer = false;
|
||||
|
||||
mapSubtypes(exprType, (subtype) => {
|
||||
if (isAnyOrUnknown(subtype)) {
|
||||
resultTypes.push(subtype);
|
||||
return subtype;
|
||||
}
|
||||
|
||||
if (isClassInstance(subtype) && isCPtrType(subtype)) {
|
||||
const typeArgs = subtype.priv.typeArgs;
|
||||
if (typeArgs && typeArgs.length > 0) {
|
||||
const pointee = typeArgs[0];
|
||||
resultTypes.push(
|
||||
isClass(pointee) && !isClassInstance(pointee) ? convertToInstance(pointee) : pointee
|
||||
);
|
||||
} else {
|
||||
// 裸 CPtr(无类型参数)无法确定目标类型。
|
||||
resultTypes.push(UnknownType.create());
|
||||
}
|
||||
return subtype;
|
||||
}
|
||||
|
||||
// 非指针类型:C 语义报错。
|
||||
sawNonPointer = true;
|
||||
return subtype;
|
||||
});
|
||||
|
||||
if (sawNonPointer) {
|
||||
evaluator.addDiagnostic(
|
||||
DiagnosticRule.reportOperatorIssue,
|
||||
LocMessage.typeNotSupportUnaryOperator().format({
|
||||
operator: printOperator(node.d.operator),
|
||||
type: evaluator.printType(exprType),
|
||||
}),
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
if (resultTypes.length === 0) {
|
||||
return { type: UnknownType.create(isIncomplete), isIncomplete };
|
||||
}
|
||||
|
||||
return { type: combineTypes(resultTypes), isIncomplete };
|
||||
}
|
||||
|
||||
// Viper 扩展:类型注解上下文的一元 '*' → C 指针类型 CPtr[T](即 `T*` 后缀语法)。
|
||||
// 与 `T | t.CPtr` 联合化简(combineTypes → CPtr[T])的表示一致:
|
||||
// 类对象(instantiable)先转为实例再作为 CPtr 的类型参数;TypeVar 直接作为类型参数
|
||||
// (泛型中的 `T*` → CPtr[T])。每个嵌套层递归求值,`T**` 自然得到 CPtr[CPtr[T]]。
|
||||
function getTypeOfViperPointerAnnotation(evaluator: TypeEvaluator, node: UnaryOperationNode): TypeResult {
|
||||
const innerResult = evaluator.getTypeOfExpression(node.d.expr, EvalFlags.TypeExpression);
|
||||
const innerType = innerResult.type;
|
||||
const resultTypes: Type[] = [];
|
||||
let sawInvalidType = false;
|
||||
|
||||
mapSubtypes(innerType, (subtype) => {
|
||||
if (isAnyOrUnknown(subtype)) {
|
||||
resultTypes.push(subtype);
|
||||
return subtype;
|
||||
}
|
||||
|
||||
const typeArg = isClass(subtype) && !isClassInstance(subtype) ? convertToInstance(subtype) : subtype;
|
||||
const cptrType = getViperCPtrTypeForTypeArg(evaluator, node, typeArg);
|
||||
if (cptrType) {
|
||||
resultTypes.push(cptrType);
|
||||
return subtype;
|
||||
}
|
||||
|
||||
sawInvalidType = true;
|
||||
return subtype;
|
||||
});
|
||||
|
||||
if (sawInvalidType) {
|
||||
evaluator.addDiagnostic(
|
||||
DiagnosticRule.reportInvalidTypeForm,
|
||||
LocMessage.typeNotSupportUnaryOperator().format({
|
||||
operator: printOperator(node.d.operator),
|
||||
type: evaluator.printType(innerType),
|
||||
}),
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
if (resultTypes.length === 0) {
|
||||
return { type: UnknownType.create(innerResult.isIncomplete), isIncomplete: innerResult.isIncomplete };
|
||||
}
|
||||
|
||||
return { type: combineTypes(resultTypes), isIncomplete: innerResult.isIncomplete };
|
||||
}
|
||||
|
||||
// Viper 扩展:一元 '&'(取地址)类型求值,与 C 语义一致。
|
||||
// - x 类型为 X → 返回 CPtr[X](即 `X | t.CPtr` 的 Viper 指针表示)
|
||||
function getTypeOfViperAddrOf(
|
||||
evaluator: TypeEvaluator,
|
||||
node: UnaryOperationNode,
|
||||
exprType: Type,
|
||||
isIncomplete: boolean | undefined
|
||||
): TypeResult {
|
||||
const cptrType = getViperCPtrTypeForTypeArg(evaluator, node, exprType);
|
||||
if (!cptrType) {
|
||||
return { type: UnknownType.create(isIncomplete), isIncomplete };
|
||||
}
|
||||
return { type: cptrType, isIncomplete };
|
||||
}
|
||||
|
||||
// 构造 CPtr[typeArg](Viper 指针类型),通过 t 模块的 CPtr 泛型类特化。
|
||||
function getViperCPtrTypeForTypeArg(
|
||||
evaluator: TypeEvaluator,
|
||||
node: UnaryOperationNode,
|
||||
typeArg: Type
|
||||
): Type | undefined {
|
||||
const tSymbolWithScope = evaluator.lookUpSymbolRecursive(node, 't', /* honorCodeFlow */ true);
|
||||
if (!tSymbolWithScope) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tEffectiveType = evaluator.getEffectiveTypeOfSymbolForUsage(
|
||||
tSymbolWithScope.symbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (!isModule(tEffectiveType)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cptrSymbol = ModuleType.getField(tEffectiveType, 'CPtr');
|
||||
if (!cptrSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cptrType = evaluator.getEffectiveTypeOfSymbolForUsage(
|
||||
cptrSymbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (isInstantiableClass(cptrType)) {
|
||||
return convertToInstance(ClassType.specialize(cptrType, [typeArg]));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getTypeOfTernaryOperation(
|
||||
evaluator: TypeEvaluator,
|
||||
node: TernaryNode,
|
||||
@@ -1382,9 +1563,26 @@ function validateArithmeticOperation(
|
||||
ClassType.isBuiltIn(leftSubtypeExpanded, ['int', 'float', 'bool', 'bytes', 'str']);
|
||||
const rightIsBuiltinNum = isClassInstance(rightSubtypeExpanded) &&
|
||||
ClassType.isBuiltIn(rightSubtypeExpanded, ['int', 'float', 'bool', 'bytes', 'str']);
|
||||
// str/bytes 指针算术优先:一方是 str/bytes(i8* 指针)、另一方是
|
||||
// 整数(含 C 整数类型)且运算符为 + 或 - 时,不走 CType 分支
|
||||
// (否则 `str + CInt` 会误返回 CInt),由下方指针算术分支处理。
|
||||
// 注意:`int - str` 不是指针算术(指针不能减整数的反向),
|
||||
// 如 `CChar - '0'` 数字字符转换仍走 CType 分支。
|
||||
const leftIsStrBytes = isClassInstance(leftSubtypeExpanded) &&
|
||||
ClassType.isBuiltIn(leftSubtypeExpanded, ['str', 'bytes']);
|
||||
const rightIsStrBytes = isClassInstance(rightSubtypeExpanded) &&
|
||||
ClassType.isBuiltIn(rightSubtypeExpanded, ['str', 'bytes']);
|
||||
const isPointerArith =
|
||||
(operator === OperatorType.Add &&
|
||||
((leftIsStrBytes && isViperIntegerType(rightSubtypeExpanded)) ||
|
||||
(rightIsStrBytes && isViperIntegerType(leftSubtypeExpanded)))) ||
|
||||
(operator === OperatorType.Subtract &&
|
||||
leftIsStrBytes &&
|
||||
(isViperIntegerType(rightSubtypeExpanded) || rightIsStrBytes));
|
||||
// 任一方是 CType 子类,另一方是 CType 子类或 Python 内置数值/字符串类型
|
||||
if ((leftIsCType && (rightIsCType || rightIsBuiltinNum)) ||
|
||||
(rightIsCType && (leftIsCType || leftIsBuiltinNum))) {
|
||||
if (!isPointerArith &&
|
||||
((leftIsCType && (rightIsCType || rightIsBuiltinNum)) ||
|
||||
(rightIsCType && (leftIsCType || leftIsBuiltinNum)))) {
|
||||
// 算术/位运算结果类型:优先返回 CType 子类(保持类型语义)
|
||||
if (leftIsCType) {
|
||||
return leftSubtypeExpanded;
|
||||
@@ -1405,10 +1603,11 @@ function validateArithmeticOperation(
|
||||
ClassType.isBuiltIn(leftSubtypeExpanded, ['str', 'bytes']);
|
||||
const rightIsStrBytes = isClassInstance(rightSubtypeExpanded) &&
|
||||
ClassType.isBuiltIn(rightSubtypeExpanded, ['str', 'bytes']);
|
||||
const leftIsInt = isClassInstance(leftSubtypeExpanded) &&
|
||||
ClassType.isBuiltIn(leftSubtypeExpanded, 'int');
|
||||
const rightIsInt = isClassInstance(rightSubtypeExpanded) &&
|
||||
ClassType.isBuiltIn(rightSubtypeExpanded, 'int');
|
||||
// 整数操作数须涵盖 Viper C 整数类型(CInt 等),否则
|
||||
// `str + CInt`(如 resp_buf + total)会落入下方 CType 算术分支
|
||||
// 误返回整数类型,而指针偏移应保持 str/bytes(i8*)指针类型。
|
||||
const leftIsInt = isViperIntegerType(leftSubtypeExpanded);
|
||||
const rightIsInt = isViperIntegerType(rightSubtypeExpanded);
|
||||
if (operator === OperatorType.Add) {
|
||||
if ((leftIsStrBytes && rightIsInt) || (leftIsInt && rightIsStrBytes)) {
|
||||
// 返回指针类型(str/bytes)
|
||||
|
||||
@@ -462,7 +462,11 @@ export class PackageTypeVerifier {
|
||||
if (isFile) {
|
||||
const fileExtension = getFileExtension(entry.name);
|
||||
|
||||
if (fileExtension === '.vp' || fileExtension === '.vpi') {
|
||||
if (
|
||||
fileExtension === '.vp' ||
|
||||
fileExtension === '.vpi' ||
|
||||
fileExtension === '.vpnci'
|
||||
) {
|
||||
const nameWithoutExtension = stripFileExtension(entry.name);
|
||||
|
||||
if (nameWithoutExtension === '__init__') {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { appendArray } from '../common/collectionUtils';
|
||||
import { assert } from '../common/debug';
|
||||
import { DiagnosticAddendum } from '../common/diagnostic';
|
||||
import { IsViperDialect } from '../common/dialect';
|
||||
import { DiagnosticRule } from '../common/diagnosticRules';
|
||||
import { LocAddendum, LocMessage } from '../localization/localize';
|
||||
import {
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
PatternValueNode,
|
||||
} from '../parser/parseNodes';
|
||||
import { CodeFlowReferenceExpressionNode } from './codeFlowTypes';
|
||||
import { getFileInfo } from './analyzerNodeInfo';
|
||||
import { addConstraintsForExpectedType } from './constraintSolver';
|
||||
import { ConstraintTracker } from './constraintTracker';
|
||||
import { DeclarationType } from './declaration';
|
||||
@@ -833,6 +835,7 @@ function narrowTypeBasedOnClassPattern(
|
||||
pattern: PatternClassNode,
|
||||
isPositiveTest: boolean
|
||||
): Type {
|
||||
const isViperDialect = IsViperDialect(getFileInfo(pattern).executionEnvironment?.dialect);
|
||||
let exprType = evaluator.getTypeOfExpression(pattern.d.className, EvalFlags.CallBaseDefaults).type;
|
||||
|
||||
// If this is a class (but not a type alias that refers to a class),
|
||||
@@ -1058,6 +1061,33 @@ function narrowTypeBasedOnClassPattern(
|
||||
}
|
||||
|
||||
if (isClassInstance(subjectSubtypeExpanded)) {
|
||||
// Viper 扩展:指针 subject 的变体模式收窄。
|
||||
// `match x: case LLVMType.Struct(...)` 中 subject 为 CPtr[LLVMType](指针)时,
|
||||
// 收窄结果应是 CPtr[LLVMType.Struct](指向变体的指针),而非普通变体值 Struct。
|
||||
// 否则 case 体内继续把 x 传给 CPtr 参数(如 build_alloca(builder, x))会误报
|
||||
// "Struct 不可分配给 CPtr[LLVMType]"。
|
||||
// 判定用 fullName 前缀(LLVMType.Struct ⊂ LLVMType):REnum 变体类与
|
||||
// 基类之间 assignType 不一定成立(变体不直接继承基类),故不能用 assignType。
|
||||
if (
|
||||
isViperDialect &&
|
||||
isCPtrType(subjectSubtypeExpanded) &&
|
||||
isInstantiableClass(expandedSubtype)
|
||||
) {
|
||||
const subjectTypeArgs = subjectSubtypeExpanded.priv.typeArgs;
|
||||
const subjectPointee =
|
||||
subjectTypeArgs && subjectTypeArgs.length > 0 ? subjectTypeArgs[0] : undefined;
|
||||
if (
|
||||
subjectPointee &&
|
||||
isClassInstance(subjectPointee) &&
|
||||
!isAnyOrUnknown(subjectPointee) &&
|
||||
expandedSubtype.shared.fullName.startsWith(subjectPointee.shared.fullName + '.')
|
||||
) {
|
||||
const patternInstance = ClassType.cloneAsInstance(expandedSubtype);
|
||||
const cptrResult = ClassType.specialize(subjectSubtypeExpanded, [patternInstance]);
|
||||
return addConditionToType(cptrResult, getTypeCondition(subjectSubtypeExpanded));
|
||||
}
|
||||
}
|
||||
|
||||
let resultType: Type;
|
||||
|
||||
if (evaluator.assignType(ClassType.cloneAsInstance(expandedSubtype), subjectSubtypeExpanded)) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { OperationCanceledException, throwIfCancellationRequested } from '../com
|
||||
import { ConfigOptions, ExecutionEnvironment, matchFileSpecs } from '../common/configOptions';
|
||||
import { ConsoleInterface, StandardConsole } from '../common/console';
|
||||
import { isThenable } from '../common/core';
|
||||
import { appendArray } from '../common/collectionUtils';
|
||||
import * as debug from '../common/debug';
|
||||
import { assert } from '../common/debug';
|
||||
import { Diagnostic, DiagnosticCategory } from '../common/diagnostic';
|
||||
@@ -30,6 +31,8 @@ import { Range, TextRange, doRangesIntersect } from '../common/textRange';
|
||||
import { Duration, timingStats } from '../common/timing';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ParseFileResults, ParserOutput } from '../parser/parser';
|
||||
import { MemberAccessNode, ParseNodeType } from '../parser/parseNodes';
|
||||
import { DeclarationType } from './declaration';
|
||||
import { RequiringAnalysisCount } from './analysis';
|
||||
import { AbsoluteModuleDescriptor, ImportLookupResult, LookupImportOptions } from './analyzerFileInfo';
|
||||
import { CellChainIndex, CellChainIndexProvider } from './cellChainIndex';
|
||||
@@ -157,6 +160,10 @@ export class Program {
|
||||
private _editModeTracker = new EditModeTracker();
|
||||
private _sourceFileFactory: ISourceFileFactory;
|
||||
|
||||
// Viper 扩展:includes/builtins.vp(真实源码,非 .vpi 存根)的 typedef 符号
|
||||
// 合并状态。key = builtinsImport.uri.key,避免对每个文件重复扫描/合并。
|
||||
private _viperBuiltinsVpMergedKeys = new Set<string>();
|
||||
|
||||
constructor(
|
||||
initialImportResolver: ImportResolver,
|
||||
initialConfigOptions: ConfigOptions,
|
||||
@@ -287,6 +294,9 @@ export class Program {
|
||||
this._configOptions = configOptions;
|
||||
this._importResolver.setConfigOptions(configOptions);
|
||||
|
||||
// Viper 扩展:配置变化(includes 等搜索路径调整)后重置 builtins.vp 合并状态。
|
||||
this._viperBuiltinsVpMergedKeys.clear();
|
||||
|
||||
// Create a new evaluator with the updated config options.
|
||||
this._createNewEvaluator();
|
||||
}
|
||||
@@ -1879,6 +1889,13 @@ export class Program {
|
||||
// the builtin module.
|
||||
builtinsScope =
|
||||
getScopeIfAvailable(fileToBind.chainedSourceFile) ?? getScopeIfAvailable(fileToBind.builtinsImport);
|
||||
|
||||
// Viper 扩展:将项目 includes 中 builtins.vp(真实源码,非 .vpi 存根)
|
||||
// 的 t.CTypedef 类型别名(rowptr/size_t 等)合并进 builtin 作用域。
|
||||
// 对齐 TransPyC TypeAnnotationResolver / TPV _lookup_builtins_typedef 的
|
||||
// "builtins 最后匹配"语义:builtins 始终纳入编译链但不在用户文件的
|
||||
// import 闭包内,其 typedef 声明需作为注解名解析的兜底。
|
||||
this._mergeViperBuiltinsVpSymbols(fileToBind, builtinsScope);
|
||||
}
|
||||
|
||||
if (fileToBind.sourceFile.isParseRequired()) {
|
||||
@@ -1902,6 +1919,125 @@ export class Program {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Viper 扩展:将项目 includes 中 builtins.vp(真实源码,非 .vpi 存根)的
|
||||
// t.CTypedef 类型别名(rowptr/size_t 等)合并进 builtin 作用域。
|
||||
//
|
||||
// 背景:TransPyC/TPV 的"builtins 最后匹配"——builtins 模块始终纳入编译链,
|
||||
// 但不在用户文件的 import 闭包内,其 `NAME: t.CTypedef = TYPE` 声明(如
|
||||
// builtins.vp 中的 rowptr = t.CPtr、size_t = __stdint.size_t)不随
|
||||
// from_imports 注入,编译器按源文件扫描 builtins.vp 作为注解名解析兜底
|
||||
// (见 TransPyC lib/core/TypeAnnotationResolver.py)。
|
||||
//
|
||||
// LSP 侧 builtin 作用域仍以 builtins.vpi(typeshed 存根,提供 int/str/Asm 等)
|
||||
// 为基准,这里仅补充 builtins.vp 中 builtin 作用域缺失的 typedef 符号,
|
||||
// 不覆盖既有内建语义。
|
||||
private _mergeViperBuiltinsVpSymbols(fileToBind: SourceFileInfo, builtinsScope: Scope | undefined) {
|
||||
if (!builtinsScope || !fileToBind.builtinsImport) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = fileToBind.builtinsImport.uri.key;
|
||||
if (this._viperBuiltinsVpMergedKeys.has(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先标记(防止绑定 builtins.vp 时递归进入本方法),成功后保留该标记。
|
||||
this._viperBuiltinsVpMergedKeys.add(key);
|
||||
|
||||
// 在 workspace root / 项目 includes(extraPaths)中定位 builtins.vp。
|
||||
// 注意:用 fileToBind.uri 而非 builtinsImport.uri(后者位于 typeshed,
|
||||
// findExecEnvironment 会落到默认环境而丢失 extraPaths)。
|
||||
const execEnv = this._configOptions.findExecEnvironment(fileToBind.uri);
|
||||
const candidates: Uri[] = [];
|
||||
if (execEnv?.root) {
|
||||
candidates.push(execEnv.root);
|
||||
}
|
||||
if (execEnv) {
|
||||
appendArray(candidates, execEnv.extraPaths);
|
||||
}
|
||||
|
||||
// Viper 扩展:真实编译器始终把 stdlib 的 includes 目录纳入编译链,
|
||||
// 与工程是否在 .vpj / viperconfig 中声明 includes 无关。这里额外沿
|
||||
// "被分析文件" 与 "执行环境根" 的祖先目录上溯(最多 6 层),把每个
|
||||
// 祖先目录自身及其 includes/ 子目录也纳入 builtins.vp 的候选位置,
|
||||
// 使 `<root>/includes/builtins.vp` 对 `<root>/Test/.../App/main.vp`
|
||||
// 这类未显式配置 includes 的工程同样可用(对齐编译器行为)。
|
||||
const ancestorKeys = new Set<string>(candidates.map((c) => c.key));
|
||||
const seeds: Uri[] = [];
|
||||
if (fileToBind.uri && !fileToBind.uri.isEmpty()) {
|
||||
seeds.push(fileToBind.uri.getDirectory());
|
||||
}
|
||||
if (execEnv?.root) {
|
||||
seeds.push(execEnv.root);
|
||||
}
|
||||
for (const seed of seeds) {
|
||||
let dir: Uri | undefined = seed;
|
||||
for (let depth = 0; depth < 6 && dir && !dir.isEmpty(); depth++) {
|
||||
for (const candidateDir of [dir, dir.combinePathsUnsafe('includes')]) {
|
||||
if (ancestorKeys.has(candidateDir.key)) {
|
||||
continue;
|
||||
}
|
||||
ancestorKeys.add(candidateDir.key);
|
||||
candidates.push(candidateDir);
|
||||
}
|
||||
const parent = dir.getDirectory();
|
||||
if (parent.equals(dir)) {
|
||||
break;
|
||||
}
|
||||
dir = parent;
|
||||
}
|
||||
}
|
||||
|
||||
let builtinsVpUri: Uri | undefined;
|
||||
for (const dir of candidates) {
|
||||
const candidate = dir.combinePathsUnsafe('builtins').addExtension('.vp');
|
||||
if (this.fileSystem.existsSync(candidate)) {
|
||||
builtinsVpUri = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!builtinsVpUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载并绑定 builtins.vp(_lookUpImport 会按需绑定,递归安全)。
|
||||
this.addTrackedFile(builtinsVpUri);
|
||||
this._lookUpImport(builtinsVpUri);
|
||||
|
||||
const builtinsVpInfo = this.getSourceFileInfo(builtinsVpUri);
|
||||
const parseResults = builtinsVpInfo?.sourceFile.getParserOutput();
|
||||
if (!builtinsVpInfo || !parseResults) {
|
||||
return;
|
||||
}
|
||||
|
||||
const builtinsVpScope = AnalyzerNodeInfo.getScope(parseResults.parseTree);
|
||||
if (!builtinsVpScope) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 合并:builtins.vp 的声明优先覆盖 builtin 作用域(完全对齐真实编译器,
|
||||
// 如 ord/chr 的 t.CChar 签名、min/max 的 double 签名、rowptr/size_t 等 typedef)。
|
||||
// 覆盖范围:t.CTypedef 类型别名声明 + 函数声明;跳过模块别名(__stdint/t)
|
||||
// 与隐式 dunder 符号,避免引入不必要的隐式全局。
|
||||
for (const [name, symbol] of builtinsVpScope.symbolTable) {
|
||||
const decls = symbol.getDeclarations();
|
||||
const isViperDecl = decls.some(
|
||||
(decl) =>
|
||||
(decl.type === DeclarationType.Variable &&
|
||||
!!decl.typeAnnotationNode &&
|
||||
decl.typeAnnotationNode.nodeType === ParseNodeType.MemberAccess &&
|
||||
(decl.typeAnnotationNode as MemberAccessNode).d.member.d.value === 'CTypedef') ||
|
||||
decl.type === DeclarationType.Function
|
||||
);
|
||||
if (!isViperDecl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
builtinsScope.symbolTable.set(name, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
private _getEffectiveFutureImports(futureImports: Set<string>, chainedSourceFile: SourceFileInfo): Set<string> {
|
||||
const effectiveFutureImports = new Set<string>(futureImports);
|
||||
|
||||
|
||||
@@ -284,7 +284,7 @@ export class SourceFile {
|
||||
this._uri = uri;
|
||||
this._fileId = this._makeFileId(uri);
|
||||
this._moduleNameGetter = moduleNameGetter;
|
||||
this._isStubFile = uri.hasExtension('.vpi');
|
||||
this._isStubFile = uri.hasExtension('.vpi') || uri.hasExtension('.vpnci');
|
||||
this._isThirdPartyImport = isThirdPartyImport;
|
||||
this._isThirdPartyPyTypedPresent = isThirdPartyPyTypedPresent;
|
||||
const fileName = uri.fileName;
|
||||
|
||||
@@ -1128,7 +1128,7 @@ export class SourceMapper {
|
||||
}
|
||||
|
||||
export function isStubFile(uri: Uri): boolean {
|
||||
return uri.lastExtension === '.vpi';
|
||||
return uri.lastExtension === '.vpi' || uri.lastExtension === '.vpnci';
|
||||
}
|
||||
|
||||
export function isAliasFactoryCallee(callTarget: ExpressionNode, allowMemberAccess = false): boolean {
|
||||
|
||||
@@ -4900,6 +4900,23 @@ export function createTypeEvaluator(
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseNodeType.UnaryOperation: {
|
||||
// Viper 扩展:`*p = value`(C 指针解引用写回)。
|
||||
// 将 value 赋给 p 指向的类型(pointee)。仅 Viper 方言、一元乘法(解引用)。
|
||||
if (
|
||||
target.d.operator === OperatorType.Multiply &&
|
||||
IsViperDialect(AnalyzerNodeInfo.getFileInfo(target).executionEnvironment.dialect)
|
||||
) {
|
||||
// 解引用目标类型 = p 指向的类型(getTypeOfViperDeref)。
|
||||
const derefTypeResult = getTypeOfExpression(target);
|
||||
assignType(derefTypeResult.type, typeResult.type, expectedTypeDiagAddendum);
|
||||
writeTypeCache(target, derefTypeResult, EvalFlags.None);
|
||||
break;
|
||||
}
|
||||
addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.assignmentTargetExpr(), target);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.assignmentTargetExpr(), target);
|
||||
break;
|
||||
@@ -6218,7 +6235,15 @@ export function createTypeEvaluator(
|
||||
case TypeCategory.Never: {
|
||||
// Viper 扩展:Unknown 类型成员访问时标黄警告"对象可能未定义"。
|
||||
// 主要用于未解析的导入模块(如 import serial 失败后 serial.puts 访问)。
|
||||
if (isUnknown(baseType) && IsViperDialect(fileInfo.executionEnvironment.dialect)) {
|
||||
// 仅对"最终" Unknown(isIncomplete=false)报错:isIncomplete=true 表示
|
||||
// 类型分析中间态(依赖尚未收敛,如循环 import 中方法绑定未完成),
|
||||
// 此时报"对象可能未定义"是误报(收敛后类型会修正,如
|
||||
// TransPyC StructGenMixin.get_mro_chain 内 self.find_struct)。
|
||||
if (
|
||||
isUnknown(baseType) &&
|
||||
!(baseType.priv.isIncomplete ?? false) &&
|
||||
IsViperDialect(fileInfo.executionEnvironment.dialect)
|
||||
) {
|
||||
fileInfo.diagnosticSink.addDiagnosticWithTextRange(
|
||||
'warning',
|
||||
LocMessage.viperUnknownMemberAccess().format({
|
||||
@@ -6381,9 +6406,19 @@ export function createTypeEvaluator(
|
||||
isClassInstance(baseType) &&
|
||||
isViperREnumClass(baseType)
|
||||
) {
|
||||
const variantFieldType = getViperREnumVariantFieldType(baseType, memberName);
|
||||
if (variantFieldType) {
|
||||
typeResult = { type: variantFieldType };
|
||||
// Viper 扩展:REnum 自带 kind() 方法。
|
||||
// REnum 内存布局为 {i32 __tag, <max_variant_payload>},
|
||||
// kind() 读取 __tag 判别值(对齐 TPC 的 _EmitREnumLlvm),
|
||||
// 返回 t.CInt(与 ast.AST.kind() 一致)。
|
||||
if (memberName === 'kind') {
|
||||
const kindFunctionType = FunctionType.createSynthesizedInstance('kind');
|
||||
kindFunctionType.shared.declaredReturnType = getBuiltInObject(node.d.member, 'int');
|
||||
typeResult = { type: kindFunctionType };
|
||||
} else {
|
||||
const variantFieldType = getViperREnumVariantFieldType(baseType, memberName);
|
||||
if (variantFieldType) {
|
||||
typeResult = { type: variantFieldType };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9324,6 +9359,39 @@ export function createTypeEvaluator(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Viper 扩展:判断调用节点是否为内置 enumerate 调用。
|
||||
// Viper 语义下 enumerate 的迭代元素类型按 for 原迭代逻辑计算
|
||||
//(str/bytes = i8* → t.CChar、CPtr[T] → T),索引类型为 t.CSizeT(size_t)。
|
||||
function isViperEnumerateCall(node: CallNode, baseTypeResult: TypeResult): boolean {
|
||||
return isInstantiableClass(baseTypeResult.type) && ClassType.isBuiltIn(baseTypeResult.type, 'enumerate');
|
||||
}
|
||||
|
||||
// Viper 扩展:计算 enumerate(...) 迭代产生的元组类型 tuple[CSizeT, 元素类型]。
|
||||
// 若非 Viper 方言下的 enumerate 调用则返回 undefined。
|
||||
function getViperEnumerateIteratedType(expr: ExpressionNode): Type | undefined {
|
||||
if (!IsViperDialect(AnalyzerNodeInfo.getFileInfo(expr).executionEnvironment.dialect)) {
|
||||
return undefined;
|
||||
}
|
||||
if (expr.nodeType !== ParseNodeType.Call) {
|
||||
return undefined;
|
||||
}
|
||||
const callNode = expr as CallNode;
|
||||
const calleeTypeResult = getTypeOfExpression(callNode.d.leftExpr);
|
||||
if (!isInstantiableClass(calleeTypeResult.type) || !ClassType.isBuiltIn(calleeTypeResult.type, 'enumerate')) {
|
||||
return undefined;
|
||||
}
|
||||
const argExpr = callNode.d.args.length >= 1 ? callNode.d.args[0].d.valueExpr : undefined;
|
||||
const elementType = argExpr
|
||||
? getTypeOfIterator(getTypeOfExpression(argExpr), /* isAsync */ false, argExpr)?.type ??
|
||||
UnknownType.create()
|
||||
: UnknownType.create();
|
||||
const indexType = getViperSizeTType(expr) ?? getBuiltInObject(expr, 'int');
|
||||
return makeTupleObject(evaluatorInterface, [
|
||||
{ type: indexType, isUnbounded: false },
|
||||
{ type: elementType, isUnbounded: false },
|
||||
]);
|
||||
}
|
||||
|
||||
// Viper 扩展:判断调用节点是否为 stdarg.va_arg 调用。
|
||||
// va_arg 通过 `from stdarg import va_arg` 引入,baseTypeResult 为 FunctionType,
|
||||
// 其 shared.name 为 'va_arg'。TPV 将 va_arg 视为 builtin,第二参数是类型名。
|
||||
@@ -9839,6 +9907,27 @@ export function createTypeEvaluator(
|
||||
} else {
|
||||
typeResult.type = getViperSizeTType(node) ?? getBuiltInObject(node, 'int');
|
||||
}
|
||||
} else if (
|
||||
// Viper 扩展:拦截内置 enumerate 调用。
|
||||
// Python 声明 `enumerate(iterable: Iterable[_T]) -> enumerate[_T]`,
|
||||
// 但 str/bytes(i8*)实参无法满足 Iterable[_T] 协议匹配,
|
||||
// 导致 _T 求解为 Unknown(`for i, ch in enumerate(str)` 中 i/ch 均变 Unknown)。
|
||||
// 此处按 for 原迭代逻辑(getTypeOfIterator)计算元素类型并直接特化 enumerate:
|
||||
// str/bytes → t.CChar、CPtr[T] → T 等。索引类型由 for 语句特判
|
||||
//(getViperEnumerateIteratedType)提供 t.CSizeT。
|
||||
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) &&
|
||||
isViperEnumerateCall(node, baseTypeResult)
|
||||
) {
|
||||
const argExpr = node.d.args.length >= 1 ? node.d.args[0].d.valueExpr : undefined;
|
||||
const argTypeResult = argExpr ? getTypeOfExpression(argExpr) : undefined;
|
||||
const elementType = argTypeResult
|
||||
? getTypeOfIterator(argTypeResult, /* isAsync */ false, argExpr!)?.type ?? UnknownType.create()
|
||||
: UnknownType.create();
|
||||
const enumerateType = getBuiltInType(node, 'enumerate');
|
||||
typeResult.type =
|
||||
isInstantiableClass(enumerateType) && elementType
|
||||
? ClassType.cloneAsInstance(ClassType.specialize(enumerateType, [elementType]))
|
||||
: UnknownType.create();
|
||||
} else {
|
||||
const callResult = validateCallArgs(
|
||||
node,
|
||||
@@ -9902,6 +9991,40 @@ export function createTypeEvaluator(
|
||||
typeResult = { type: UnknownType.create() };
|
||||
}
|
||||
|
||||
// Viper 扩展:返回类型为 t.State 的函数是 C 风格 noreturn(仅声明不定义,
|
||||
// 如 sys.exit / stdlib.exit 的 `-> t.State`)。调用此类函数后流程终止(Never),
|
||||
// 使 `if x is None: fatal_error(...)` 模式能正确窄化——fatal_error 内部调用
|
||||
// sys.exit(1) 返回 Never 后,fatal_error 被推断为 NoReturn,if 的 true 分支
|
||||
// 终止流程,后续代码不会错误地保留 None 分支(否则 `x.Ty.Pointee` 会把
|
||||
// x.Ty 解析为 `None | CPtr[LLVMType]` 并误报 "None 没有属性 Pointee")。
|
||||
if (
|
||||
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) &&
|
||||
!typeResult.typeErrors &&
|
||||
!isNever(typeResult.type) &&
|
||||
isFunctionOrOverloaded(baseTypeResult.type)
|
||||
) {
|
||||
let hasViperStateReturn = false;
|
||||
doForEachSubtype(baseTypeResult.type, (subtype) => {
|
||||
if (isFunction(subtype)) {
|
||||
const declaredReturn = subtype.shared.declaredReturnType;
|
||||
if (declaredReturn) {
|
||||
doForEachSubtype(declaredReturn, (retSubtype) => {
|
||||
if (
|
||||
isClass(retSubtype) &&
|
||||
isCTypeSubclassType(retSubtype) &&
|
||||
retSubtype.shared.name === 'State'
|
||||
) {
|
||||
hasViperStateReturn = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (hasViperStateReturn) {
|
||||
typeResult.type = NeverType.createNever();
|
||||
}
|
||||
}
|
||||
|
||||
return typeResult;
|
||||
}
|
||||
|
||||
@@ -18366,6 +18489,17 @@ export function createTypeEvaluator(
|
||||
EvalFlags.NoTypeVarTuple |
|
||||
EvalFlags.NoClassVar;
|
||||
|
||||
// Viper 扩展:保留上方"裸类引用 RHS 不专用化"的 NoSpecialize 标志。
|
||||
// 否则 `rowptr: t.CTypedef = t.CPtr` 的 RHS 会被 reportMissingTypeArgs
|
||||
// 预填成 CPtr[Unknown],导致 `rowptr[int]` 无法再特化
|
||||
// (下标处报"类型 CPtr[Unknown] 已专用化")。
|
||||
if (
|
||||
node.d.rightExpr.nodeType === ParseNodeType.Name ||
|
||||
node.d.rightExpr.nodeType === ParseNodeType.MemberAccess
|
||||
) {
|
||||
flags |= EvalFlags.NoSpecialize;
|
||||
}
|
||||
|
||||
typeAliasNameNode = (node.d.leftExpr as TypeAnnotationNode).d.valueExpr as NameNode;
|
||||
|
||||
if (!isLegalTypeAliasExpressionForm(node.d.rightExpr, /* allowStrLiteral */ true)) {
|
||||
@@ -21337,8 +21471,18 @@ export function createTypeEvaluator(
|
||||
}
|
||||
|
||||
const iteratorTypeResult = getTypeOfExpression(node.d.iterableExpr);
|
||||
const iteratedType =
|
||||
getTypeOfIterator(iteratorTypeResult, !!node.d.isAsync, node.d.iterableExpr)?.type ?? UnknownType.create();
|
||||
let iteratedType: Type;
|
||||
// Viper 扩展:`for i, ch in enumerate(x)` 中 enumerate 迭代产生
|
||||
// tuple[t.CSizeT, 元素类型] —— 索引为 size_t(C 语义数组下标),
|
||||
// 元素类型按 for 原迭代逻辑计算(str/bytes → t.CChar、CPtr[T] → T)。
|
||||
const enumerateIteratedType = getViperEnumerateIteratedType(node.d.iterableExpr);
|
||||
if (enumerateIteratedType) {
|
||||
iteratedType = enumerateIteratedType;
|
||||
} else {
|
||||
iteratedType =
|
||||
getTypeOfIterator(iteratorTypeResult, !!node.d.isAsync, node.d.iterableExpr)?.type ??
|
||||
UnknownType.create();
|
||||
}
|
||||
|
||||
assignTypeToExpression(
|
||||
node.d.targetExpr,
|
||||
@@ -26422,15 +26566,25 @@ export function createTypeEvaluator(
|
||||
}
|
||||
}
|
||||
|
||||
// Viper 扩展:整数 ↔ 指针隐式转换语义。
|
||||
// 在 C/Viper 语义中,整数(地址值)与指针类型可相互转换:
|
||||
// - 整数地址 → CPtr/CVoid(如 framebuffer_addr: CUnsignedLong 赋值给 CVoid | CPtr)
|
||||
// - 指针 → 整数(如 uintptr_t = (uintptr_t)ptr)
|
||||
// 这模拟 C 语言的整数-指针转换规则。
|
||||
// Viper 扩展:整数 ↔ 指针隐式转换语义(C 空指针常量规则)。
|
||||
// C 语言中只有"值为 0 的整型常量表达式"(null pointer constant)可以
|
||||
// 隐式转换为任意指针类型(NULL);其他整数值(如地址值)必须显式转换。
|
||||
// - 整数字面量 0 → CPtr/CVoid:允许(NULL 空指针)
|
||||
// - 其他整数 → 指针:不允许,需显式转换(如 t.CUInt32T(addr, t.CPtr))
|
||||
// - 指针 → 整数:允许(如 uintptr_t = (uintptr_t)ptr)
|
||||
const destIsPtrLike = isCPtrType(destType) || isCVoidType(destType);
|
||||
const srcIsPtrLike = isCPtrType(srcType) || isCVoidType(srcType);
|
||||
if (destIsPtrLike && isViperIntegerType(srcType)) {
|
||||
return true;
|
||||
// 仅整数字面量 0(null pointer constant)可隐式转换为指针;
|
||||
// 变量/常量即使值为 0 也不是字面量(C 语义同样拒绝)。
|
||||
if (isClassInstance(srcType) && srcType.priv.literalValue === 0) {
|
||||
return true;
|
||||
}
|
||||
diag?.addMessage(
|
||||
`仅允许整数字面量 0 隐式转换为指针(NULL 空指针),` +
|
||||
`其他整数值需显式转换(如 t.CInt(值, t.CPtr))`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (srcIsPtrLike && isViperIntegerType(destType)) {
|
||||
return true;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import { assert } from '../common/debug';
|
||||
import { IsViperDialect } from '../common/dialect';
|
||||
import {
|
||||
ArgCategory,
|
||||
AssignmentExpressionNode,
|
||||
@@ -45,6 +46,7 @@ import {
|
||||
isAnyOrUnknown,
|
||||
isClass,
|
||||
isClassInstance,
|
||||
isCPtrType,
|
||||
isFunction,
|
||||
isFunctionOrOverloaded,
|
||||
isInstantiableClass,
|
||||
@@ -175,7 +177,11 @@ export function getTypeNarrowingCallback(
|
||||
)
|
||||
) {
|
||||
return (type: Type) => {
|
||||
return { type: narrowTypeForIsNone(evaluator, type, adjIsPositiveTest), isIncomplete: false };
|
||||
const isViperDialect = IsViperDialect(getFileInfo(testExpression).executionEnvironment?.dialect);
|
||||
return {
|
||||
type: narrowTypeForIsNone(evaluator, type, adjIsPositiveTest, isViperDialect),
|
||||
isIncomplete: false,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1108,7 +1114,12 @@ function getInnermostNewTypeBaseInstance(subtype: Type): Type | undefined {
|
||||
}
|
||||
|
||||
// Handle type narrowing for expressions of the form "x is None" and "x is not None".
|
||||
function narrowTypeForIsNone(evaluator: TypeEvaluator, type: Type, isPositiveTest: boolean) {
|
||||
function narrowTypeForIsNone(
|
||||
evaluator: TypeEvaluator,
|
||||
type: Type,
|
||||
isPositiveTest: boolean,
|
||||
isViperDialect = false
|
||||
) {
|
||||
const expandedType = mapSubtypes(type, (subtype) => {
|
||||
return transformPossibleRecursiveTypeAlias(subtype);
|
||||
});
|
||||
@@ -1172,6 +1183,15 @@ function narrowTypeForIsNone(evaluator: TypeEvaluator, type: Type, isPositiveTes
|
||||
return isPositiveTest ? adjustedSubtype : undefined;
|
||||
}
|
||||
|
||||
// Viper 扩展:None 即 C 空指针 NULL,是 CPtr 指针类型的一个取值,而非独立类型。
|
||||
// `p is None` 的正测试只是空指针判断,不改变指针类型本身(与 C 语义一致)。
|
||||
// 若不拦截,正分支会把 CPtr[T] 窄化为 NoneType,随后与负分支合并时
|
||||
// combineTypes 会把 None 误当作结构体候选折叠成 CPtr[None],
|
||||
// 导致 `if p is None: fatal_error(...)` 守卫之后 `.Pointee` 等成员访问误报。
|
||||
if (isViperDialect && isClassInstance(subtype) && isCPtrType(subtype)) {
|
||||
return adjustedSubtype;
|
||||
}
|
||||
|
||||
// Is it potentially None?
|
||||
if (evaluator.assignType(subtype, evaluator.getNoneType())) {
|
||||
resultIncludesNoneSubtype = true;
|
||||
|
||||
@@ -4153,7 +4153,8 @@ export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): T
|
||||
// Viper 扩展:将 Struct | CPtr 自动映射为 CPtr[Struct]。
|
||||
// 模拟 C 语言指针语义:结构体与指针的联合类型自动转换为指向该结构体的指针类型。
|
||||
// 例如:Scheduler | t.CPtr → CPtr[Scheduler],bootinfo.bootinfo | t.CPtr → CPtr[bootinfo.bootinfo]。
|
||||
// 识别条件:联合类型中同时存在 CPtr 子类型和"结构体"子类型(非 CPtr/CVoid/CArray/CConst/CDefine/整数/str/bytes)。
|
||||
// 识别条件:联合类型中同时存在 CPtr 子类型和"结构体"子类型(非 CPtr/CVoid/CArray/CConst/CDefine/str/bytes)。
|
||||
// int 允许作为结构体候选:`int | t.CPtr` → CPtr[int](C 的 int*),与 `t.CInt | t.CPtr` 一致。
|
||||
if (subtypes.length > 1) {
|
||||
let cptrSubtype: ClassType | undefined;
|
||||
const structSubtypes: ClassType[] = [];
|
||||
@@ -4167,12 +4168,13 @@ export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): T
|
||||
}
|
||||
} else if (
|
||||
isClass(subtype) &&
|
||||
// None 是空指针取值而非结构体,不能作为 CPtr 的类型参数(否则 None | CPtr[T] 会误折叠成 CPtr[None])
|
||||
!ClassType.isBuiltIn(subtype, 'NoneType') &&
|
||||
!isCVoidType(subtype) &&
|
||||
!isCArrayType(subtype) &&
|
||||
!isCConstType(subtype) &&
|
||||
!isCStaticType(subtype) &&
|
||||
!isCDefineType(subtype) &&
|
||||
!ClassType.isBuiltIn(subtype, 'int') &&
|
||||
!ClassType.isBuiltIn(subtype, 'str') &&
|
||||
!ClassType.isBuiltIn(subtype, 'bytes') &&
|
||||
!ClassType.isBuiltIn(subtype, 'object')
|
||||
|
||||
@@ -118,7 +118,7 @@ class DefaultTypeshedInfoProvider implements TypeshedInfoProvider {
|
||||
typeshedThirdPartyPackagePaths.set(innerEntry.name, [innerDirPath]);
|
||||
}
|
||||
} else if (innerEntry.isFile()) {
|
||||
if (innerEntry.name.endsWith('.vpi')) {
|
||||
if (innerEntry.name.endsWith('.vpi') || innerEntry.name.endsWith('.vpnci')) {
|
||||
const strippedFileName = stripFileExtension(innerEntry.name);
|
||||
const pathList = typeshedThirdPartyPackagePaths.get(strippedFileName);
|
||||
if (pathList) {
|
||||
|
||||
@@ -1229,6 +1229,8 @@ function getKeywordTypeString(type: KeywordType) {
|
||||
return 'Debug';
|
||||
case KeywordType.Def:
|
||||
return 'Def';
|
||||
case KeywordType.Define:
|
||||
return 'Define';
|
||||
case KeywordType.Del:
|
||||
return 'Del';
|
||||
case KeywordType.Elif:
|
||||
|
||||
@@ -606,7 +606,7 @@ export function getWildcardRoot(rootPath: string, fileSpec: string): string {
|
||||
}
|
||||
|
||||
export function hasPythonExtension(path: string) {
|
||||
return path.endsWith('.vp') || path.endsWith('.vpi');
|
||||
return path.endsWith('.vp') || path.endsWith('.vpi') || path.endsWith('.vpnci');
|
||||
}
|
||||
|
||||
export function getRegexEscapedSeparator(pathSep: string = path.sep) {
|
||||
|
||||
@@ -334,7 +334,7 @@ export function getWildcardRoot(root: Uri, fileSpec: string): Uri {
|
||||
}
|
||||
|
||||
export function hasPythonExtension(uri: Uri) {
|
||||
return uri.hasExtension('.vp') || uri.hasExtension('.vpi');
|
||||
return uri.hasExtension('.vp') || uri.hasExtension('.vpi') || uri.hasExtension('.vpnci');
|
||||
}
|
||||
|
||||
export function getFileSpec(root: Uri, fileSpec: string): FileSpec {
|
||||
|
||||
@@ -479,8 +479,9 @@ export class AutoImporter {
|
||||
const fileDir = uri.getDirectory();
|
||||
const initPathPy = fileDir.initPyUri;
|
||||
const initPathPyi = fileDir.initPyiUri;
|
||||
const isStub = uri.hasExtension('.vpi');
|
||||
const hasInit = map.has(initPathPy.key) || map.has(initPathPyi.key);
|
||||
const initPathVpnci = fileDir.combinePathsUnsafe('__init__.vpnci');
|
||||
const isStub = uri.hasExtension('.vpi') || uri.hasExtension('.vpnci');
|
||||
const hasInit = map.has(initPathPy.key) || map.has(initPathPyi.key) || map.has(initPathVpnci.key);
|
||||
const sourceFileInfo = this.program.getSourceFileInfo(uri);
|
||||
return { isStub, hasInit, isUserCode: isUserCode(sourceFileInfo) };
|
||||
}
|
||||
|
||||
@@ -47,7 +47,16 @@ import { convertOffsetToPosition, convertPositionToOffset } from '../common/posi
|
||||
import { ServiceProvider } from '../common/serviceProvider';
|
||||
import { Position, Range, TextRange } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ExpressionNode, FunctionNode, NameNode, ParseNode, ParseNodeType, StringNode } from '../parser/parseNodes';
|
||||
import {
|
||||
ExpressionNode,
|
||||
FunctionNode,
|
||||
NameNode,
|
||||
ParamCategory,
|
||||
ParameterNode,
|
||||
ParseNode,
|
||||
ParseNodeType,
|
||||
StringNode,
|
||||
} from '../parser/parseNodes';
|
||||
import { ParseFileResults } from '../parser/parser';
|
||||
import { TokenType } from '../parser/tokenizerTypes';
|
||||
import {
|
||||
@@ -600,6 +609,10 @@ export class HoverProvider {
|
||||
);
|
||||
|
||||
this._addResultsPart(parts, signatureString, /* python */ true);
|
||||
|
||||
// Viper 扩展:hover 函数时附加详尽调试信息(仅 Viper 方言)。
|
||||
this._addViperFunctionDebugPart(parts, resolvedDecl);
|
||||
|
||||
this._addDocumentationPart(parts, node, resolvedDecl);
|
||||
break;
|
||||
}
|
||||
@@ -624,6 +637,120 @@ export class HoverProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// Viper 扩展:hover 函数时附加详尽调试信息(仅 Viper 方言)。
|
||||
// 显示:定义位置、修饰符(extern/export/inline)、泛型参数、参数列表(类型/默认值)、返回类型。
|
||||
private _addViperFunctionDebugPart(parts: HoverTextPart[], resolvedDecl: FunctionDeclaration): void {
|
||||
const funcNode = resolvedDecl.node;
|
||||
if (funcNode.nodeType !== ParseNodeType.Function) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileInfo = getFileInfo(funcNode);
|
||||
if (!IsViperDialect(fileInfo.executionEnvironment.dialect)) {
|
||||
return;
|
||||
}
|
||||
// 不对 stub 文件中的声明附加调试信息(如 stdlib .vpi / typeshed .pyi 类型定义),
|
||||
// 避免对纯声明函数产生噪声。
|
||||
const declPathLower = fileInfo.fileUri.getPath().toLowerCase();
|
||||
if (declPathLower.endsWith('.pyi') || declPathLower.endsWith('.vpi') || declPathLower.endsWith('.vpnci')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const declUri = fileInfo.fileUri;
|
||||
const declParseResults = this._program.getParseResults(declUri);
|
||||
if (!declParseResults) {
|
||||
return;
|
||||
}
|
||||
const declText = declParseResults.text;
|
||||
const declLines = declParseResults.parserOutput.lines;
|
||||
|
||||
const isMarkdown = this._format === MarkupKind.Markdown;
|
||||
const code = (s: string) => (isMarkdown ? `\`${s}\`` : s);
|
||||
const bold = (s: string) => (isMarkdown ? `**${s}**` : s);
|
||||
const lines: string[] = [];
|
||||
|
||||
// 定义位置(文件:行:列)
|
||||
const namePos = convertOffsetToPosition(funcNode.d.name.start, declLines);
|
||||
const locDisplay = declUri.key === this._fileUri.key ? '此文件' : declUri.getPath();
|
||||
lines.push(`${bold('位置')}: ${code(`${locDisplay}:${namePos.line + 1}:${namePos.character + 1}`)}`);
|
||||
|
||||
// 修饰符(extern/export/inline 等 C 修饰关键字)
|
||||
if (funcNode.d.viperModifier) {
|
||||
lines.push(`${bold('修饰符')}: ${code(funcNode.d.viperModifier)}`);
|
||||
}
|
||||
|
||||
// 泛型参数(PEP 695 type 参数)
|
||||
if (funcNode.d.typeParams && funcNode.d.typeParams.d.params.length > 0) {
|
||||
const typeParamNames = funcNode.d.typeParams.d.params.map((p) => p.d.name.d.value).join(', ');
|
||||
lines.push(`${bold('泛型参数')}: ${code(`[${typeParamNames}]`)}`);
|
||||
}
|
||||
|
||||
// 参数列表(名称、类型、默认值、*args/**kwargs 类别)
|
||||
if (funcNode.d.params.length > 0) {
|
||||
lines.push(`${bold('参数')}:`);
|
||||
for (const param of funcNode.d.params) {
|
||||
const paramText = this._getViperParamDebugText(param, declText);
|
||||
if (paramText) {
|
||||
lines.push(` ${code(paramText)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回类型
|
||||
const returnType = this._getViperFunctionReturnType(funcNode);
|
||||
if (returnType) {
|
||||
lines.push(`${bold('返回类型')}: ${code(this._evaluator.printType(convertToInstance(returnType)))}`);
|
||||
}
|
||||
|
||||
if (lines.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const heading = isMarkdown ? '**函数调试信息**' : '函数调试信息';
|
||||
parts.push({ text: heading + '\n' + lines.join('\n'), python: false });
|
||||
}
|
||||
|
||||
// 构造单个参数的调试文本:`*名称: 类型 = 默认值`。
|
||||
private _getViperParamDebugText(param: ParameterNode, declText: string): string | undefined {
|
||||
let text = '';
|
||||
if (param.d.category === ParamCategory.ArgsList) {
|
||||
text += '*';
|
||||
} else if (param.d.category === ParamCategory.KwargsDict) {
|
||||
text += '**';
|
||||
}
|
||||
text += param.d.name?.d.value ?? '(匿名)';
|
||||
|
||||
let paramType: Type | undefined;
|
||||
if (param.d.annotation) {
|
||||
paramType = this._evaluator.getTypeOfAnnotation(param.d.annotation);
|
||||
} else if (param.d.name) {
|
||||
paramType = this._evaluator.getTypeOfExpression(param.d.name).type;
|
||||
}
|
||||
if (paramType) {
|
||||
text += ': ' + this._evaluator.printType(convertToInstance(paramType));
|
||||
}
|
||||
|
||||
if (param.d.defaultValue) {
|
||||
const defaultText = declText.slice(param.d.defaultValue.start, TextRange.getEnd(param.d.defaultValue));
|
||||
text += ` = ${defaultText}`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// 返回类型:优先取注解类型,无注解时从函数类型的声明/推断返回类型提取。
|
||||
private _getViperFunctionReturnType(funcNode: FunctionNode): Type | undefined {
|
||||
if (funcNode.d.returnAnnotation) {
|
||||
return this._evaluator.getTypeOfAnnotation(funcNode.d.returnAnnotation);
|
||||
}
|
||||
|
||||
const fnType = this._getType(funcNode.d.name);
|
||||
if (isFunction(fnType)) {
|
||||
return fnType.shared.declaredReturnType ?? fnType.shared.inferredReturnType?.type;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _addResultsForSynthesizedType(parts: HoverTextPart[], typeInfo: SynthesizedTypeInfo, hoverNode: NameNode) {
|
||||
let typeText: string | undefined;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import * as AnalyzerNodeInfo from '../analyzer/analyzerNodeInfo';
|
||||
import { Declaration, DeclarationType, VariableDeclaration } from '../analyzer/declaration';
|
||||
import { Declaration, DeclarationType, FunctionDeclaration, VariableDeclaration } from '../analyzer/declaration';
|
||||
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
|
||||
import { isWithinTypeAnnotation } from '../analyzer/parseTreeUtils';
|
||||
import { TypeCategory, isCDefineType } from '../analyzer/types';
|
||||
@@ -619,7 +619,14 @@ class SemanticTokenWalker extends ParseTreeWalker {
|
||||
memberAccessType.category !== TypeCategory.Unknown &&
|
||||
memberAccessType.category !== TypeCategory.Unbound
|
||||
) {
|
||||
tokenType = SemanticTokenTypes.property;
|
||||
// Viper 扩展:成员访问可求值时,若为函数类型(如 REnum 自带
|
||||
// kind()、CPtr 解引用转发的方法)显示为方法(函数颜色);
|
||||
// 数据字段显示为 property。
|
||||
tokenType =
|
||||
memberAccessType.category === TypeCategory.Function ||
|
||||
memberAccessType.category === TypeCategory.Overloaded
|
||||
? SemanticTokenTypes.function
|
||||
: SemanticTokenTypes.property;
|
||||
} else {
|
||||
tokenType = SemanticTokenTypes.undefined;
|
||||
}
|
||||
@@ -647,7 +654,12 @@ class SemanticTokenWalker extends ParseTreeWalker {
|
||||
tokenModifiers |= defMod;
|
||||
break;
|
||||
case DeclarationType.Function:
|
||||
tokenType = SemanticTokenTypes.function;
|
||||
// Viper 扩展:define 宏函数名(定义处与调用处)显示为 macro(深蓝色)。
|
||||
if ((decl as FunctionDeclaration).node.d.isDefine) {
|
||||
tokenType = SemanticTokenTypes.macro;
|
||||
} else {
|
||||
tokenType = SemanticTokenTypes.function;
|
||||
}
|
||||
tokenModifiers |= defMod;
|
||||
break;
|
||||
case DeclarationType.Param:
|
||||
|
||||
@@ -38,6 +38,8 @@ export const enum SemanticTokenTypes {
|
||||
decorator = 12,
|
||||
/** 属性 */
|
||||
property = 13,
|
||||
/** 宏函数(define 定义的编译期宏,显示为深蓝色) */
|
||||
macro = 14,
|
||||
}
|
||||
|
||||
/** 语义 token 修饰符(位掩码) */
|
||||
@@ -70,6 +72,7 @@ export const semanticTokenTypesLegend: string[] = [
|
||||
'undefined',
|
||||
'decorator',
|
||||
'property',
|
||||
'macro',
|
||||
];
|
||||
|
||||
/** LSP legend 中的 tokenModifiers 字符串列表(顺序与 bit 位对应) */
|
||||
|
||||
@@ -914,6 +914,7 @@ export namespace Localizer {
|
||||
export const returnTypeUnknown = () => getRawString('Diagnostic.returnTypeUnknown');
|
||||
export const returnTypePartiallyUnknown = () =>
|
||||
new ParameterizedString<{ returnType: string }>(getRawString('Diagnostic.returnTypePartiallyUnknown'));
|
||||
export const viperReturnExprInNoneFunction = () => getRawString('Diagnostic.viperReturnExprInNoneFunction');
|
||||
export const revealLocalsArgs = () => getRawString('Diagnostic.revealLocalsArgs');
|
||||
export const revealLocalsNone = () => getRawString('Diagnostic.revealLocalsNone');
|
||||
export const revealTypeArgs = () => getRawString('Diagnostic.revealTypeArgs');
|
||||
|
||||
@@ -1169,6 +1169,7 @@
|
||||
"returnTypeMismatch": "Type \"{exprType}\" is not assignable to return type \"{returnType}\"",
|
||||
"returnTypePartiallyUnknown": "Return type, \"{returnType}\", is partially unknown",
|
||||
"returnTypeUnknown": "Return type is unknown",
|
||||
"viperReturnExprInNoneFunction": "A Viper function that returns None (no return value) cannot \"return\" an expression whose value is None; only a bare \"return\" or \"return None\" is allowed",
|
||||
"revealLocalsArgs": {
|
||||
"message": "Expected no arguments for \"reveal_locals\" call",
|
||||
"comment": "{Locked='reveal_locals'}"
|
||||
|
||||
@@ -455,6 +455,7 @@
|
||||
"returnTypeMismatch": "类型“{exprType}”不可分配给返回类型“{returnType}”",
|
||||
"returnTypePartiallyUnknown": "返回类型“{returnType}”部分未知",
|
||||
"returnTypeUnknown": "返回类型未知",
|
||||
"viperReturnExprInNoneFunction": "返回 None(无返回值)的函数不能用 return 返回值为 None 的表达式,只有裸 return 和 return None 合法",
|
||||
"revealLocalsArgs": "“reveal_locals”调用应没有参数",
|
||||
"revealLocalsNone": "此范围内没有 locals 变量",
|
||||
"revealTypeArgs": "“reveal_type”调用应为单个位置参数",
|
||||
|
||||
@@ -473,6 +473,8 @@ export interface FunctionNode extends ParseNodeBase<ParseNodeType.Function> {
|
||||
suite: SuiteNode;
|
||||
// Viper 扩展:函数声明前的 C 修饰关键字(inline/export/extern),用于语义校验。
|
||||
viperModifier: string | undefined;
|
||||
// Viper 扩展:define 宏函数标记(语法与 def 完全一致,语义为编译期宏展开)。
|
||||
isDefine: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -482,7 +484,8 @@ export namespace FunctionNode {
|
||||
name: NameNode,
|
||||
suite: SuiteNode,
|
||||
typeParams?: TypeParameterListNode,
|
||||
viperModifier?: string
|
||||
viperModifier?: string,
|
||||
isDefine?: boolean
|
||||
) {
|
||||
const node: FunctionNode = {
|
||||
start: defToken.start,
|
||||
@@ -502,6 +505,7 @@ export namespace FunctionNode {
|
||||
funcAnnotationComment: undefined,
|
||||
suite: suite,
|
||||
viperModifier,
|
||||
isDefine: isDefine ?? false,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -488,6 +488,10 @@ export class Parser {
|
||||
case KeywordType.Def:
|
||||
return this._parseFunctionDef();
|
||||
|
||||
case KeywordType.Define:
|
||||
// Viper 扩展:define 宏函数(与 def 语法一致,语义为编译期宏展开)。
|
||||
return this._parseFunctionDef();
|
||||
|
||||
case KeywordType.Class:
|
||||
return this._parseClassDef();
|
||||
|
||||
@@ -615,7 +619,9 @@ export class Parser {
|
||||
|
||||
const wasParsingTypeAnnotation = this._isParsingTypeAnnotation;
|
||||
this._isParsingTypeAnnotation = true;
|
||||
const expression = this._parseTestExpression(/* allowAssignmentExpression */ false);
|
||||
let expression = this._parseTestExpression(/* allowAssignmentExpression */ false);
|
||||
// Viper 扩展:类型别名同样支持 C 指针类型后缀(type P = T*)。
|
||||
expression = this._parseViperPointerTypeSuffix(expression);
|
||||
this._isParsingTypeAnnotation = wasParsingTypeAnnotation;
|
||||
|
||||
return TypeAliasNode.create(typeToken, name, expression, typeParameters);
|
||||
@@ -2075,7 +2081,23 @@ export class Parser {
|
||||
): FunctionNode | ErrorNode {
|
||||
// Viper 扩展:extern/export 直接声明形式(`extern A(...)`)没有 def 关键字,
|
||||
// 用修饰关键字 token 作为范围起点;否则正常消费 Def 关键字 token。
|
||||
const defToken = defTokenOverride ?? this._getKeywordToken(KeywordType.Def);
|
||||
// define 宏函数关键字与 def 语义一致,语法完全相同,仅标记 isDefine。
|
||||
let defToken: Token;
|
||||
let isDefine = false;
|
||||
if (defTokenOverride) {
|
||||
defToken = defTokenOverride;
|
||||
} else {
|
||||
const curToken = this._peekToken();
|
||||
if (
|
||||
curToken.type === TokenType.Keyword &&
|
||||
(curToken as KeywordToken).keywordType === KeywordType.Define
|
||||
) {
|
||||
isDefine = true;
|
||||
defToken = this._getNextToken() as KeywordToken;
|
||||
} else {
|
||||
defToken = this._getKeywordToken(KeywordType.Def);
|
||||
}
|
||||
}
|
||||
|
||||
const nameToken = this._getTokenIfIdentifier();
|
||||
if (!nameToken) {
|
||||
@@ -2156,7 +2178,7 @@ export class Parser {
|
||||
this._isInFinallyBlock = wasInFinallyBlock;
|
||||
this._isInFinallyLoop = wasInFinallyLoop;
|
||||
|
||||
const functionNode = FunctionNode.create(defToken, NameNode.create(nameToken), suite, typeParameters);
|
||||
const functionNode = FunctionNode.create(defToken, NameNode.create(nameToken), suite, typeParameters, viperModifier, isDefine);
|
||||
functionNode.d.viperModifier = viperModifier;
|
||||
if (asyncToken) {
|
||||
functionNode.d.isAsync = true;
|
||||
@@ -2538,6 +2560,9 @@ export class Parser {
|
||||
}
|
||||
} else if (nextToken.keywordType === KeywordType.Def) {
|
||||
return this._parseFunctionDef(undefined, decoratorList);
|
||||
} else if (nextToken.keywordType === KeywordType.Define) {
|
||||
// Viper 扩展:装饰的 define 宏函数。
|
||||
return this._parseFunctionDef(undefined, decoratorList);
|
||||
} else if (nextToken.keywordType === KeywordType.Class) {
|
||||
return this._parseClassDef(decoratorList);
|
||||
}
|
||||
@@ -3388,12 +3413,40 @@ export class Parser {
|
||||
// test_or_star: test | star_expr
|
||||
private _parseTestOrStarExpression(allowAssignmentExpression: boolean): ExpressionNode {
|
||||
if (this._peekOperatorType() === OperatorType.Multiply) {
|
||||
// Viper 扩展:前缀 '*' 在表达式位置可能是 C 风格一元解引用(`*p`),
|
||||
// 也可能是解包元素(`[*a]`、`(*p, q)` 中的 `*a`/`*p`)。
|
||||
// 先按一元解引用解析,若后随逗号/闭括号等分隔符(解包元素上下文)则改判为 Unpack。
|
||||
if (this._parseOptions.dialect === 'viper') {
|
||||
return this._parseViperStarExprOrDeref();
|
||||
}
|
||||
return this._parseExpression(/* allowUnpack */ true);
|
||||
}
|
||||
|
||||
return this._parseTestExpression(allowAssignmentExpression);
|
||||
}
|
||||
|
||||
// Viper 扩展:解析前缀 '*'。先消费 '*' 并以非解包模式解析其操作数,
|
||||
// 若操作数后紧跟逗号/闭括号等(表示它是解包元素),改判为 Unpack 节点;
|
||||
// 否则构造一元 '*'(解引用)节点,与 C 语义一致。
|
||||
// 注意:`)` 不触发改判——`(*p)` 是单元素括号(解引用),而 `(*p,)`/`(*p, q)`
|
||||
// 的 `*p` 后是逗号(解包元素)。
|
||||
private _parseViperStarExprOrDeref(): ExpressionNode {
|
||||
const startToken = this._peekToken();
|
||||
this._getNextToken(); // 消费 '*'
|
||||
const expression = this._parseExpression(/* allowUnpack */ false);
|
||||
|
||||
const nextType = this._peekToken().type;
|
||||
if (
|
||||
nextType === TokenType.Comma ||
|
||||
nextType === TokenType.CloseBracket ||
|
||||
nextType === TokenType.CloseCurlyBrace
|
||||
) {
|
||||
return UnpackNode.create(startToken, expression);
|
||||
}
|
||||
|
||||
return this._createUnaryOperationNode(startToken, expression, OperatorType.Multiply);
|
||||
}
|
||||
|
||||
// test: or_test ['if' or_test 'else' test] | lambdef
|
||||
private _parseTestExpression(allowAssignmentExpression: boolean): ExpressionNode {
|
||||
if (this._peekKeywordType() === KeywordType.Lambda) {
|
||||
@@ -3672,6 +3725,17 @@ export class Parser {
|
||||
nextOperator === OperatorType.Mod ||
|
||||
nextOperator === OperatorType.FloorDivide
|
||||
) {
|
||||
// Viper 扩展:类型注解上下文中的后缀 '*' 是 C 指针类型(T* = CPtr[T]),
|
||||
// 不是二元乘法。停止解析,交由 _parseTypeAnnotation/_parseTypeAliasStatement
|
||||
// 的后缀处理消费,避免 `p: T*` 被解析成 `T * <缺失操作数>` 报错。
|
||||
if (
|
||||
this._parseOptions.dialect === 'viper' &&
|
||||
this._isParsingTypeAnnotation &&
|
||||
nextOperator === OperatorType.Multiply
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
this._getNextToken();
|
||||
const rightExpr = this._parseArithmeticFactor();
|
||||
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, nextOperator);
|
||||
@@ -3682,15 +3746,21 @@ export class Parser {
|
||||
return leftExpr;
|
||||
}
|
||||
|
||||
// factor: ('+'|'-'|'~') factor | power
|
||||
// factor: ('+'|'-'|'~'|'*'|'&') factor | power
|
||||
// power: atom_expr ['**' factor]
|
||||
// Viper 扩展:一元 '*'(解引用)与 '&'(取地址)与 C 语义一致,
|
||||
// 仅在 Viper 方言下将前缀 '*'/'&' 解析为一元运算。
|
||||
private _parseArithmeticFactor(): ExpressionNode {
|
||||
const nextToken = this._peekToken();
|
||||
const nextOperator = this._peekOperatorType();
|
||||
const isViperUnaryOp =
|
||||
this._parseOptions.dialect === 'viper' &&
|
||||
(nextOperator === OperatorType.Multiply || nextOperator === OperatorType.BitwiseAnd);
|
||||
if (
|
||||
nextOperator === OperatorType.Add ||
|
||||
nextOperator === OperatorType.Subtract ||
|
||||
nextOperator === OperatorType.BitwiseInvert
|
||||
nextOperator === OperatorType.BitwiseInvert ||
|
||||
isViperUnaryOp
|
||||
) {
|
||||
this._getNextToken();
|
||||
const expression = this._parseArithmeticFactor();
|
||||
@@ -3703,7 +3773,15 @@ export class Parser {
|
||||
}
|
||||
|
||||
const peekToken = this._peekToken();
|
||||
if (this._consumeTokenIfOperator(OperatorType.Power)) {
|
||||
const nextPowerOperator = this._peekOperatorType();
|
||||
// Viper 扩展:类型注解上下文中的 '**' 是二级指针类型后缀(T** = CPtr[CPtr[T]]),
|
||||
// 词法化为 Power 算子('**'),不是幂运算。跳过幂运算分支,
|
||||
// 交由 _parseTypeAnnotation/_parseTypeAliasStatement 的后缀处理消费。
|
||||
if (
|
||||
nextPowerOperator === OperatorType.Power &&
|
||||
!(this._parseOptions.dialect === 'viper' && this._isParsingTypeAnnotation)
|
||||
) {
|
||||
this._getNextToken();
|
||||
const rightExpr = this._parseArithmeticFactor();
|
||||
return this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, OperatorType.Power);
|
||||
}
|
||||
@@ -4643,8 +4721,10 @@ export class Parser {
|
||||
|
||||
// Is this a type annotation assignment?
|
||||
if (this._consumeTokenIfType(TokenType.Colon)) {
|
||||
// 保留原始赋值目标(NameNode),后续 `*=` 拆分重包装时避免嵌套 TypeAnnotationNode。
|
||||
const targetExpr = leftExpr;
|
||||
annotationExpr = this._parseTypeAnnotation();
|
||||
leftExpr = TypeAnnotationNode.create(leftExpr, annotationExpr);
|
||||
leftExpr = TypeAnnotationNode.create(targetExpr, annotationExpr);
|
||||
|
||||
if (
|
||||
!this._parseOptions.isStubFile &&
|
||||
@@ -4653,7 +4733,31 @@ export class Parser {
|
||||
this._addSyntaxError(LocMessage.varAnnotationIllegal(), annotationExpr);
|
||||
}
|
||||
|
||||
if (!this._consumeTokenIfOperator(OperatorType.Assign)) {
|
||||
// Viper 扩展:`px: T* = &x` 无空格时,词法将 `*` 与 `=` 合并为 `*=`(同理 `**=`)。
|
||||
// 此时 `*`/`**` 是指针类型后缀、`=` 是赋值号,需拆开处理:
|
||||
// 消费 `*=` 作为一层指针(`**=` 两层),并视为已消费赋值号 `=`。
|
||||
let isViperStarEqualAssign = false;
|
||||
if (this._parseOptions.dialect === 'viper') {
|
||||
const starAssignOperator = this._peekOperatorType();
|
||||
if (
|
||||
starAssignOperator === OperatorType.MultiplyEqual ||
|
||||
starAssignOperator === OperatorType.PowerEqual
|
||||
) {
|
||||
const starToken = this._getNextToken();
|
||||
annotationExpr = this._createUnaryOperationNode(starToken, annotationExpr, OperatorType.Multiply);
|
||||
if (starAssignOperator === OperatorType.PowerEqual) {
|
||||
annotationExpr = this._createUnaryOperationNode(
|
||||
starToken,
|
||||
annotationExpr,
|
||||
OperatorType.Multiply
|
||||
);
|
||||
}
|
||||
leftExpr = TypeAnnotationNode.create(targetExpr, annotationExpr);
|
||||
isViperStarEqualAssign = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isViperStarEqualAssign && !this._consumeTokenIfOperator(OperatorType.Assign)) {
|
||||
return leftExpr;
|
||||
}
|
||||
|
||||
@@ -4839,6 +4943,11 @@ export class Parser {
|
||||
}
|
||||
|
||||
let result = this._parseTestExpression(/* allowAssignmentExpression */ false);
|
||||
|
||||
// Viper 扩展:C 指针类型后缀 T* / T** / T***(须在还原 _isParsingTypeAnnotation 之前,
|
||||
// 使 _parseArithmeticTerm/_parseArithmeticFactor 的星号守卫仍生效)。
|
||||
result = this._parseViperPointerTypeSuffix(result);
|
||||
|
||||
if (isUnpack) {
|
||||
result = UnpackNode.create(startToken, result);
|
||||
}
|
||||
@@ -4849,6 +4958,34 @@ export class Parser {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Viper 扩展:解析 C 指针类型后缀 `T*` / `T**` / `T***`。
|
||||
// 每个 '*' 对应一层指针;'**' 词法化为单个 Power 算子、等价两层指针。
|
||||
// 结果用 UnaryOperationNode(operator=Multiply) 嵌套表示:
|
||||
// T* → *(T) → CPtr[T]
|
||||
// T** → *(*T) → CPtr[CPtr[T]]
|
||||
// 仅 Viper 方言生效(Python 方言中 '*'/'**' 是二元乘法/幂运算)。
|
||||
private _parseViperPointerTypeSuffix(baseExpr: ExpressionNode): ExpressionNode {
|
||||
if (this._parseOptions.dialect !== 'viper') {
|
||||
return baseExpr;
|
||||
}
|
||||
|
||||
let result = baseExpr;
|
||||
while (true) {
|
||||
const nextOperator = this._peekOperatorType();
|
||||
if (nextOperator === OperatorType.Multiply) {
|
||||
const starToken = this._getNextToken();
|
||||
result = this._createUnaryOperationNode(starToken, result, OperatorType.Multiply);
|
||||
} else if (nextOperator === OperatorType.Power) {
|
||||
const starToken = this._getNextToken();
|
||||
result = this._createUnaryOperationNode(starToken, result, OperatorType.Multiply);
|
||||
result = this._createUnaryOperationNode(starToken, result, OperatorType.Multiply);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _reportStringTokenErrors(
|
||||
stringToken: StringToken | FStringStartToken,
|
||||
unescapedResult?: StringTokenUtils.UnescapedString
|
||||
|
||||
@@ -60,6 +60,8 @@ const _keywords: Map<string, KeywordType> = new Map([
|
||||
['continue', KeywordType.Continue],
|
||||
['__debug__', KeywordType.Debug],
|
||||
['def', KeywordType.Def],
|
||||
// Viper 扩展:define 宏函数关键字(与 def 语义一致,语法完全相同)。
|
||||
['define', KeywordType.Define],
|
||||
['del', KeywordType.Del],
|
||||
['elif', KeywordType.Elif],
|
||||
['else', KeywordType.Else],
|
||||
|
||||
@@ -121,6 +121,8 @@ export const enum KeywordType {
|
||||
Continue,
|
||||
Debug,
|
||||
Def,
|
||||
// Viper 扩展:define 宏函数关键字(与 def 语义一致,语法完全相同)。
|
||||
Define,
|
||||
Del,
|
||||
Elif,
|
||||
Else,
|
||||
|
||||
@@ -122,7 +122,10 @@ export class PartialStubService implements SupportPartialStubs {
|
||||
this._realFs.mapDirectory(
|
||||
packagePath,
|
||||
partialStubPackagePath,
|
||||
(u, fs) => u.hasExtension('.vpi') || (fs.existsSync(u) && fs.statSync(u).isDirectory())
|
||||
(u, fs) =>
|
||||
u.hasExtension('.vpi') ||
|
||||
u.hasExtension('.vpnci') ||
|
||||
(fs.existsSync(u) && fs.statSync(u).isDirectory())
|
||||
)
|
||||
);
|
||||
} catch {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
define A():
|
||||
pass
|
||||
|
||||
def B():
|
||||
A()
|
||||
@@ -0,0 +1,7 @@
|
||||
import t, c
|
||||
|
||||
export def add[T](a: t.CInt, b: t.CInt = 10) -> t.CInt:
|
||||
return a + b
|
||||
|
||||
def use():
|
||||
add(1, 2)
|
||||
@@ -0,0 +1,19 @@
|
||||
import t, c
|
||||
|
||||
def takes_ptr(p: t.CInt | t.CPtr) -> t.CInt | t.CPtr:
|
||||
return p
|
||||
|
||||
def test_zero_cast():
|
||||
p: t.CInt | t.CPtr = None
|
||||
q: t.CInt | t.CPtr = None
|
||||
p = 0
|
||||
p = 1
|
||||
x: t.CInt = 0
|
||||
p = x
|
||||
takes_ptr(0)
|
||||
takes_ptr(1)
|
||||
addr: t.CUnsignedLong = 0x1234
|
||||
p = addr
|
||||
p = t.CInt(addr, t.CPtr)
|
||||
takes_ptr(p)
|
||||
takes_ptr(q)
|
||||
@@ -0,0 +1,10 @@
|
||||
import t, llvmlite
|
||||
|
||||
def test(rhs_val: llvmlite.Value | t.CPtr) -> t.CInt:
|
||||
if rhs_val.Ty is None:
|
||||
return 0
|
||||
match rhs_val.Ty:
|
||||
case llvmlite.LLVMType.Struct(_, _, _):
|
||||
tmp: llvmlite.Value | t.CPtr = llvmlite.build_alloca(rhs_val, rhs_val.Ty)
|
||||
return 0
|
||||
return 0
|
||||
@@ -0,0 +1,19 @@
|
||||
import t, c
|
||||
|
||||
class MyEnum(t.REnum):
|
||||
class Int:
|
||||
Bits: t.CInt
|
||||
|
||||
class Ptr:
|
||||
Pointee: MyEnum | t.CPtr
|
||||
|
||||
def test_enum(e: MyEnum | t.CPtr) -> int:
|
||||
k: t.CInt = e.kind()
|
||||
k2: t.CInt = e.kind
|
||||
return k + k2
|
||||
|
||||
def test_assign() -> int:
|
||||
e: MyEnum | t.CPtr = MyEnum.Int(32)
|
||||
if e.kind() == 1:
|
||||
return 1
|
||||
return 0
|
||||
@@ -0,0 +1,42 @@
|
||||
L798 .coerce_to_type: (builder: CPtr[IRBuilder], val: CPtr[Value], target_ty: CPtr[LLVMType], node: CPtr[AST] = None, trans: CPtr[Translator] = None) -> CPtr[Value]
|
||||
L798 .ExprGenBase: CPtr[ExprGenBase]
|
||||
L798 .Gen: CPtr[Generator]
|
||||
L801 .fatal_error: (node: CPtr[AST], msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> None
|
||||
L801 .TypeGenErr: CPtr[TypeGenErr]
|
||||
L801 .Gen: CPtr[Generator]
|
||||
L802 .build_store: (builder: CPtr[IRBuilder], val: CPtr[Value], ptr: CPtr[Value]) -> None
|
||||
L807 .Subscript: Literal[ASTKind.Subscript]
|
||||
L807 .ASTKind: type[ASTKind]
|
||||
L808 .MemBuddy: type[MemBuddy]
|
||||
L808 .CPtr: type[CPtr[Unknown]]
|
||||
L808 .Pool: CPtr[MemBuddy]
|
||||
L809 .IRBuilder: type[IRBuilder]
|
||||
L809 .CPtr: type[CPtr[Unknown]]
|
||||
L809 ._cur_builder: CPtr[IRBuilder]
|
||||
L810 .LLVMModule: type[LLVMModule]
|
||||
L810 .CPtr: type[CPtr[Unknown]]
|
||||
L810 .Module: CPtr[LLVMModule]
|
||||
L813 .Value: type[Value]
|
||||
L813 .CPtr: type[CPtr[Unknown]]
|
||||
L813 .get_subscript_ptr: (builder: CPtr[IRBuilder], pool: CPtr[MemBuddy], mod: CPtr[LLVMModule], node: CPtr[AST], trans: CPtr[Translator] = None) -> CPtr[Value]
|
||||
L813 .ExprGenName: CPtr[ExprGenName]
|
||||
L813 .Gen: CPtr[Generator]
|
||||
L817 .fatal_error: (node: CPtr[AST], msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> None
|
||||
L817 .TypeGenErr: CPtr[TypeGenErr]
|
||||
L817 .Gen: CPtr[Generator]
|
||||
L820 .LLVMType: type[LLVMType]
|
||||
L820 .CPtr: type[CPtr[Unknown]]
|
||||
L821 .Ty: CPtr[LLVMType]
|
||||
L821 .Pointee: CPtr[LLVMType]
|
||||
L821 .Ty: CPtr[LLVMType]
|
||||
L822 .Pointee: CPtr[LLVMType]
|
||||
L822 .Ty: CPtr[LLVMType]
|
||||
L825 .fatal_error: (node: CPtr[AST], msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> None
|
||||
L825 .TypeGenErr: CPtr[TypeGenErr]
|
||||
L825 .Gen: CPtr[Generator]
|
||||
L826 .Value: type[Value]
|
||||
L826 .CPtr: type[CPtr[Unknown]]
|
||||
L826 .build_load: (builder: CPtr[IRBuilder], ty: CPtr[LLVMType], ptr: CPtr[Value]) -> CPtr[Value]
|
||||
L830 .fatal_error: (node: CPtr[AST], msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> None
|
||||
L830 .TypeGenErr: CPtr[TypeGenErr]
|
||||
L830 .Gen: CPtr[Generator]
|
||||
@@ -0,0 +1,10 @@
|
||||
import t, c
|
||||
|
||||
def main() -> int:
|
||||
x: int = 42
|
||||
px: int | t.CPtr = &x
|
||||
*px = 100
|
||||
y: int = *px
|
||||
pz: int* = &x
|
||||
*pz = 7
|
||||
return 0
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* viperBuiltinsVpTypedef.test.ts
|
||||
*
|
||||
* 验证 Viper 方言下注解名解析对 includes/builtins.vp(真实源码,非 .vpi 存根)
|
||||
* 中 t.CTypedef 类型别名的兜底搜索("builtins 最后匹配",对齐 TransPyC
|
||||
* TypeAnnotationResolver / TPV _lookup_builtins_typedef):
|
||||
* - `rowptr[int]` 注解 → rowptr 为 builtins.vp 中的 typedef,可解析为 CPtr[int]
|
||||
* - `size_t` 注解 → 解析为 t.CSizeT
|
||||
* - `int8` 注解 → 解析为 t.CInt8T
|
||||
* - 均无需显式 import builtins(builtins 不在用户文件 import 闭包内)
|
||||
* - 不报"未定义 rowptr/size_t/int8"
|
||||
*
|
||||
* 运行:npx jest src/tests/viperBuiltinsVpTypedef.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 { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { FunctionNode } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgram(source: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__builtins_vp_typedef__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
fs.mkdirSync(testRoot, { recursive: true });
|
||||
const filePath = path.join(testRoot, 'main.vp');
|
||||
fs.writeFileSync(filePath, source, 'utf8');
|
||||
|
||||
const projectRoot = UriEx.file(testRoot);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[UriEx.file(TRANSPYC_INCLUDES)],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
|
||||
const uri = UriEx.file(filePath);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const parseResults = sourceFile!.getParseResults();
|
||||
return { program, configOptions, parseResults, uri, serviceProvider };
|
||||
}
|
||||
|
||||
// 收集函数参数注解类型(打印名,expandTypeAlias 展开别名)
|
||||
function collectParamAnnotationTypes(
|
||||
program: Program,
|
||||
parseResults: any
|
||||
): Map<string, string[]> {
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator);
|
||||
const ev = evaluator;
|
||||
const result = new Map<string, string[]>();
|
||||
|
||||
class ParamCollector extends ParseTreeWalker {
|
||||
override visitFunction(node: FunctionNode) {
|
||||
const funcName = node.d.name.d.value;
|
||||
for (const param of node.d.params) {
|
||||
const ann = (param as any).d?.annotation;
|
||||
if (ann) {
|
||||
const t = ev.getType(ann as any);
|
||||
const list = result.get(funcName) ?? [];
|
||||
list.push(t ? program.printType(t, { expandTypeAlias: true }) : '(undefined)');
|
||||
result.set(funcName, list);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new ParamCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
test('builtins.vp(includes 版本)typedef 在注解中可直接解析', () => {
|
||||
const source = `import t
|
||||
|
||||
def f(x: rowptr[int]) -> int:
|
||||
return 0
|
||||
|
||||
def g() -> size_t:
|
||||
return 0
|
||||
|
||||
def h(a: int8) -> int:
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
// 1. 无未定义诊断
|
||||
const diagnostics = (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
|
||||
(d) => `[category=${d.category}] ${d.message}`
|
||||
);
|
||||
const undefinedDiags = diagnostics.filter((d) => d.includes('未定义') || d.includes('undefined'));
|
||||
assert.strictEqual(
|
||||
undefinedDiags.length,
|
||||
0,
|
||||
`builtins.vp typedef 不应报未定义,实际: ${JSON.stringify(undefinedDiags)}`
|
||||
);
|
||||
|
||||
// 2. 参数注解类型解析:rowptr[int] → CPtr[int]
|
||||
const types = collectParamAnnotationTypes(program, parseResults!);
|
||||
const fTypes = types.get('f');
|
||||
assert(fTypes && fTypes.some((t) => t.includes('CPtr') && t.includes('int')), `f(x) 应为 CPtr[int],实际: ${JSON.stringify(fTypes)}`);
|
||||
|
||||
// 3. 返回注解 size_t → CSizeT
|
||||
const gReturn = (() => {
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator);
|
||||
const ev = evaluator;
|
||||
let retType: string | undefined;
|
||||
class RetCollector extends ParseTreeWalker {
|
||||
override visitFunction(node: FunctionNode) {
|
||||
if (node.d.name.d.value === 'g' && node.d.returnAnnotation) {
|
||||
const t = ev.getType(node.d.returnAnnotation as any);
|
||||
retType = t ? program.printType(t, { expandTypeAlias: true }) : '(undefined)';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new RetCollector().walk(parseResults!.parserOutput.parseTree);
|
||||
return retType;
|
||||
})();
|
||||
assert(
|
||||
gReturn && (gReturn === 'CSizeT' || gReturn.includes('CSizeT')),
|
||||
`g() 返回注解 size_t 应为 CSizeT,实际: ${gReturn}`
|
||||
);
|
||||
|
||||
// 4. 参数注解 int8 → CInt8T
|
||||
const hTypes = types.get('h');
|
||||
assert(hTypes && hTypes.some((t) => t.includes('CInt8T')), `h(a) 应为 CInt8T,实际: ${JSON.stringify(hTypes)}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__builtins_vp_typedef__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('builtins.vp typedef 与 builtins.vpi 内建并存(int/str 等不被破坏)', () => {
|
||||
const source = `import t
|
||||
|
||||
def f(x: rowptr[int]) -> str:
|
||||
s: str = "ok"
|
||||
return s
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const diagnostics = (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
|
||||
(d) => `[category=${d.category}] ${d.message}`
|
||||
);
|
||||
const errors = diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
|
||||
assert.strictEqual(
|
||||
errors.length,
|
||||
0,
|
||||
`builtins.vpi 内建(str 等)应与 builtins.vp typedef 并存,实际: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__builtins_vp_typedef__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('builtins.vp 同名声明优先覆盖 builtins.vpi(ord 参数为 t.CChar)', () => {
|
||||
const source = `import t
|
||||
|
||||
def f() -> int:
|
||||
c: t.CChar = ord("a")
|
||||
return c
|
||||
`;
|
||||
const { program, parseResults, serviceProvider } = buildProgram(source);
|
||||
|
||||
// ord 在 builtins.vp 中声明为 ord(s: t.CChar) -> int,
|
||||
// 覆盖 builtins.vpi 的 ord(c: str | bytes | bytearray, /) -> int。
|
||||
// 通过 main 文件作用域链(含 builtin 作用域)解析 ord 符号。
|
||||
const AnalyzerNodeInfo = require('../analyzer/analyzerNodeInfo');
|
||||
const mainScope = AnalyzerNodeInfo.getScope(parseResults!.parserOutput.parseTree);
|
||||
const ordSymbol = mainScope?.lookUpSymbolRecursive?.('ord')?.symbol;
|
||||
assert(ordSymbol, 'ord 符号未找到(builtins.vp 覆盖未生效)');
|
||||
|
||||
const ordType = program.getTypeOfSymbol(ordSymbol);
|
||||
const ordSignature = program.printType(ordType, { expandTypeAlias: true });
|
||||
assert(
|
||||
ordSignature.includes('CChar'),
|
||||
`ord 应使用 builtins.vp 的 t.CChar 签名,实际: ${ordSignature}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__builtins_vp_typedef__'), { recursive: true, force: true });
|
||||
});
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* viperDefineMacro.test.ts
|
||||
*
|
||||
* 验证 Viper define 宏函数关键字(语法与 def 完全一致,语义为编译期宏展开):
|
||||
* - `define square(x) -> int` 解析为 FunctionNode 且 isDefine=true(普通 def 为 false)
|
||||
* - 宏函数名在定义处与调用处显示为 macro(深蓝色);普通 def 函数名仍为 function
|
||||
* - 宏函数与普通函数一样参与类型分析(无诊断错误)
|
||||
*
|
||||
* 运行:npx jest src/tests/viperDefineMacro.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { FunctionNode } from '../parser/parseNodes';
|
||||
import { SemanticTokenTypes } from '../languageService/semanticTokenTypes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
function buildProgram(files: Record<string, string>) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__define_macro__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
for (const [relPath, content] of Object.entries(files)) {
|
||||
const absPath = path.join(testRoot, relPath);
|
||||
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
||||
fs.writeFileSync(absPath, content, '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 uris = Object.keys(files).map((relPath) => UriEx.file(path.join(testRoot, relPath)));
|
||||
const urisMap: Record<string, any> = {};
|
||||
for (const relPath of Object.keys(files)) {
|
||||
urisMap[relPath] = UriEx.file(path.join(testRoot, relPath));
|
||||
}
|
||||
program.setTrackedFiles(uris);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
return { program, configOptions, urisMap, testRoot, 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}`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 语义 token 辅助:解码 SemanticTokens 相对编码,返回 line 级 token 映射。
|
||||
* 返回 Map<line, Array<{start, text, tokenType, tokenModifiers}>>
|
||||
*/
|
||||
function getSemanticTokens(program: Program, uri: any): Map<number, any[]> {
|
||||
const { SemanticTokenProvider } = require('../languageService/semanticTokenProvider');
|
||||
const { LogTracker } = require('../common/logTracker');
|
||||
const provider = new SemanticTokenProvider(new LogTracker());
|
||||
const result = provider.provideFull(program, uri);
|
||||
assert(result, 'no semantic tokens produced');
|
||||
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const lines = sourceFile!.getParseResults()!.text.split('\n');
|
||||
|
||||
const map = new Map<number, any[]>();
|
||||
let cursorLine = 0;
|
||||
let cursorChar = 0;
|
||||
for (let i = 0; i < result.data.length; i += 5) {
|
||||
const deltaLine = result.data[i];
|
||||
const deltaStart = result.data[i + 1];
|
||||
const length = result.data[i + 2];
|
||||
const tokenType = result.data[i + 3];
|
||||
const tokenMod = result.data[i + 4];
|
||||
cursorLine += deltaLine;
|
||||
cursorChar = deltaLine === 0 ? cursorChar + deltaStart : deltaStart;
|
||||
const lineText = lines[cursorLine] || '';
|
||||
const text = lineText.substring(cursorChar, cursorChar + length);
|
||||
if (!map.has(cursorLine)) {
|
||||
map.set(cursorLine, []);
|
||||
}
|
||||
map.get(cursorLine)!.push({ start: cursorChar, text, tokenType, tokenModifiers: tokenMod });
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
test('define 宏函数:解析为 FunctionNode 且 isDefine=true,普通 def 为 false,无诊断错误', () => {
|
||||
const { program, configOptions, urisMap } = buildProgram({
|
||||
'main.vp': `define A():
|
||||
pass
|
||||
|
||||
def B():
|
||||
A()
|
||||
`,
|
||||
});
|
||||
|
||||
// 1. 无诊断错误(宏函数与普通函数一样参与类型分析)
|
||||
const mainDiags = getDiagnostics(program, configOptions, urisMap['main.vp']);
|
||||
assert(
|
||||
mainDiags.length === 0,
|
||||
`main.vp 不应有诊断,实际: ${JSON.stringify(mainDiags)}`
|
||||
);
|
||||
|
||||
// 2. AST:A 是 define 宏函数(isDefine=true),B 是普通函数(isDefine=false)
|
||||
const sourceFile = program.getSourceFile(urisMap['main.vp']);
|
||||
const parseResults = sourceFile!.getParseResults();
|
||||
assert(parseResults, 'parse results not found');
|
||||
|
||||
const isDefineMap = new Map<string, boolean>();
|
||||
class FunctionIsDefineWalker extends ParseTreeWalker {
|
||||
override visitFunction(node: FunctionNode): boolean {
|
||||
isDefineMap.set(node.d.name.d.value, node.d.isDefine);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new FunctionIsDefineWalker().walk(parseResults.parserOutput.parseTree);
|
||||
assert(isDefineMap.get('A') === true, 'A 应标记为 define 宏函数');
|
||||
assert(isDefineMap.get('B') === false, 'B 应为普通函数(isDefine=false)');
|
||||
});
|
||||
|
||||
test('define 宏函数:定义处与调用处的函数名显示为 macro,普通 def 仍为 function', () => {
|
||||
const { program, urisMap } = buildProgram({
|
||||
'main.vp': `define A():
|
||||
pass
|
||||
|
||||
def B():
|
||||
A()
|
||||
`,
|
||||
});
|
||||
|
||||
const tokens = getSemanticTokens(program, urisMap['main.vp']);
|
||||
const lineTokens = (line: number) => tokens.get(line) || [];
|
||||
|
||||
// 第 0 行 `define A():`:A 定义处 → macro
|
||||
const defineTokens = lineTokens(0).filter((t) => t.text === 'A');
|
||||
assert(defineTokens.length === 1, '应找到 A 定义处语义 token');
|
||||
assert(defineTokens[0].tokenType === SemanticTokenTypes.macro, 'A 定义处应为 macro');
|
||||
|
||||
// 第 4 行 ` A()`:A 调用处 → macro
|
||||
const callTokens = lineTokens(4).filter((t) => t.text === 'A');
|
||||
assert(callTokens.length === 1, '应找到 A 调用处语义 token');
|
||||
assert(callTokens[0].tokenType === SemanticTokenTypes.macro, 'A 调用处应为 macro');
|
||||
|
||||
// 第 3 行 `def B():`:B 定义处 → function(普通函数不受影响)
|
||||
const bDef = lineTokens(3).find((t) => t.text === 'B');
|
||||
assert(bDef, '应找到 B 定义处语义 token');
|
||||
assert(bDef.tokenType === SemanticTokenTypes.function, 'B 定义处应为 function');
|
||||
});
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* viperEnumerate.test.ts
|
||||
*
|
||||
* 验证 Viper 方言下内置 enumerate 的语义:
|
||||
* - enumerate(str) 调用类型为 enumerate[t.CChar](_T 按 for 原迭代逻辑绑定元素类型)
|
||||
* - `for i, ch in enumerate(str)` 中 i 为 t.CSizeT(size_t 索引)、ch 为迭代元素 t.CChar
|
||||
* - 与 Python 声明(Iterable[_T] 协议、tuple[int, _T])的差异由 Viper 特判覆盖
|
||||
*
|
||||
* 运行:npx jest src/tests/viperEnumerate.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 { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { ForNode, CallNode, ExpressionNode, ParseNodeType } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
function buildProgram(source: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__enumerate__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
fs.mkdirSync(testRoot, { recursive: true });
|
||||
const filePath = path.join(testRoot, 'main.vp');
|
||||
fs.writeFileSync(filePath, source, 'utf8');
|
||||
|
||||
const projectRoot = UriEx.file(testRoot);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[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()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const parseResults = sourceFile!.getParseResults();
|
||||
return { program, configOptions, parseResults, uri, serviceProvider };
|
||||
}
|
||||
|
||||
// 收集所有 for 循环:iterable 表达式类型 + target 解包变量类型
|
||||
function collectForTargets(
|
||||
program: Program,
|
||||
parseResults: any
|
||||
): { iterableTypes: string[]; targets: Map<string, string[]> } {
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator);
|
||||
const ev = evaluator;
|
||||
const iterableTypes: string[] = [];
|
||||
const targets = new Map<string, string[]>();
|
||||
|
||||
class ForCollector extends ParseTreeWalker {
|
||||
override visitFor(node: ForNode) {
|
||||
const iterableType = ev.getType(node.d.iterableExpr as any);
|
||||
if (iterableType) {
|
||||
iterableTypes.push(program.printType(iterableType));
|
||||
}
|
||||
const targetExpr = node.d.targetExpr;
|
||||
if (targetExpr.nodeType === ParseNodeType.Tuple) {
|
||||
for (const item of (targetExpr as any).d.items as ExpressionNode[]) {
|
||||
if (item.nodeType === ParseNodeType.Name) {
|
||||
const name = (item as any).d.value as string;
|
||||
const t = ev.getType(item as any);
|
||||
const list = targets.get(name) ?? [];
|
||||
list.push(t ? program.printType(t) : '(undefined)');
|
||||
targets.set(name, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new ForCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
return { iterableTypes, targets };
|
||||
}
|
||||
|
||||
test('for i, ch in enumerate(kw_arg):i 为 t.CSizeT、ch 为 t.CChar', () => {
|
||||
const source = `import t
|
||||
|
||||
def f(kw_arg: str) -> int:
|
||||
for i, ch in enumerate(kw_arg):
|
||||
pass
|
||||
return 0
|
||||
`;
|
||||
const { program, parseResults, serviceProvider } = buildProgram(source);
|
||||
|
||||
const { iterableTypes, targets } = collectForTargets(program, parseResults!);
|
||||
|
||||
// 1. enumerate 调用类型:元素类型按 for 原迭代逻辑(str → t.CChar)
|
||||
assert(iterableTypes.some((t) => t === 'enumerate[CChar]'), `实际: ${JSON.stringify(iterableTypes)}`);
|
||||
|
||||
// 2. 索引 i 为 t.CSizeT(size_t)
|
||||
const iTypes = targets.get('i');
|
||||
assert(iTypes && iTypes.some((t) => t === 'CSizeT'), `i 应为 CSizeT,实际: ${JSON.stringify(iTypes)}`);
|
||||
|
||||
// 3. 元素 ch 为 t.CChar
|
||||
const chTypes = targets.get('ch');
|
||||
assert(chTypes && chTypes.some((t) => t === 'CChar'), `ch 应为 CChar,实际: ${JSON.stringify(chTypes)}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__enumerate__'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('enumerate 迭代 CPtr 元素时 ch 为元素类型 T', () => {
|
||||
const source = `import t
|
||||
|
||||
def f(items: t.CPtr[int]) -> int:
|
||||
for i, item in enumerate(items):
|
||||
pass
|
||||
return i
|
||||
`;
|
||||
const { program, parseResults, serviceProvider } = buildProgram(source);
|
||||
|
||||
const { iterableTypes, targets } = collectForTargets(program, parseResults!);
|
||||
|
||||
// 1. CPtr[int] 迭代 → 元素类型 int,enumerate 特化为 enumerate[int]
|
||||
assert(iterableTypes.some((t) => t === 'enumerate[int]'), `实际: ${JSON.stringify(iterableTypes)}`);
|
||||
|
||||
// 2. 索引 i 仍为 t.CSizeT
|
||||
const iTypes = targets.get('i');
|
||||
assert(iTypes && iTypes.some((t) => t === 'CSizeT'), `i 应为 CSizeT,实际: ${JSON.stringify(iTypes)}`);
|
||||
|
||||
// 3. 元素 item 为 int
|
||||
const itemTypes = targets.get('item');
|
||||
assert(itemTypes && itemTypes.some((t) => t === 'int'), `item 应为 int,实际: ${JSON.stringify(itemTypes)}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__enumerate__'), { recursive: true, force: true });
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* viperForLoopVarAfter.test.ts(文件:viperEnumerateUnbound.test.ts)
|
||||
*
|
||||
* 验证 Viper 方言下 for 循环变量在循环后的语义:
|
||||
* - `for i, ch in enumerate(kw_arg)` 后,i 保留迭代类型 t.CSizeT(C 风格),
|
||||
* 而非 Python 语义的 `Unbound | CSizeT`(循环可能零次执行)
|
||||
* - 循环后 `param_name[start + i + 1]` 的加法不再报"未绑定/加法违法"
|
||||
*
|
||||
* 运行:npx jest src/tests/viperEnumerateUnbound.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 { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { BinaryOperationNode, ForNode } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
function buildProgram(source: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__enumerate_unbound__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
fs.mkdirSync(testRoot, { recursive: true });
|
||||
const filePath = path.join(testRoot, 'main.vp');
|
||||
fs.writeFileSync(filePath, source, 'utf8');
|
||||
|
||||
const projectRoot = UriEx.file(testRoot);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[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()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const parseResults = sourceFile!.getParseResults();
|
||||
return { program, configOptions, parseResults, uri, serviceProvider };
|
||||
}
|
||||
|
||||
test('for enumerate 后 i 的类型与诊断 dump', () => {
|
||||
const source = `import t
|
||||
|
||||
def f(kw_arg: str, param_name: str) -> int:
|
||||
start: t.CSizeT = 0
|
||||
if param_name[0] == '%':
|
||||
start = 1
|
||||
for i, ch in enumerate(kw_arg):
|
||||
c: t.CChar = param_name[start + i]
|
||||
if c == '\\0' or c != ch:
|
||||
return 0
|
||||
if param_name[start + i + 1] != '\\0':
|
||||
return 0
|
||||
return 1
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator);
|
||||
assert(parseResults);
|
||||
const ev = evaluator;
|
||||
const lines = parseResults.parserOutput.lines;
|
||||
const out: string[] = [];
|
||||
const diags = (program.getSourceFile(uri)!.getDiagnostics(configOptions) || []).map((d) => d.message);
|
||||
|
||||
class BinOpCollector extends ParseTreeWalker {
|
||||
override visitBinaryOperation(node: BinaryOperationNode) {
|
||||
const pos = convertOffsetToPosition(node.start, lines);
|
||||
const t = ev.getType(node as any);
|
||||
out.push(
|
||||
` L${pos.line + 1} binop=${node.d.operator} type=${t ? program.printType(t) : '(undefined)'}`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new BinOpCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
// Viper(C 风格):for 循环变量在循环后仍视为已定义(保留迭代类型),
|
||||
// 循环体内与循环后的 start + i / start + i + 1 均应为纯 CSizeT(无 Unbound)。
|
||||
const addOps = out.filter((l) => l.includes('binop=0'));
|
||||
assert(addOps.length >= 3, `应存在 3 个加法运算(循环体内 1 + 循环后 2),实际: ${JSON.stringify(out)}`);
|
||||
assert(
|
||||
addOps.every((l) => l.includes('type=CSizeT') && !l.includes('Unbound') && !l.includes('Unknown')),
|
||||
`加法运算应均为纯 CSizeT,实际: ${JSON.stringify(addOps)}`
|
||||
);
|
||||
|
||||
// 不应有诊断(循环后使用 i 不再报未绑定/加法违法)
|
||||
assert(diags.length === 0, `不应有诊断,实际: ${JSON.stringify(diags)}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(path.join(__dirname, '__enumerate_unbound__'), { recursive: true, force: true });
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* viperFunctionHover.test.ts
|
||||
*
|
||||
* 验证 Viper 方言下 hover 函数时显示详尽调试信息:
|
||||
* - 定义位置(文件:行:列)
|
||||
* - 修饰符(extern/export/inline)
|
||||
* - 泛型参数
|
||||
* - 参数列表(名称、类型、默认值、*args/**kwargs 类别)
|
||||
* - 返回类型
|
||||
*
|
||||
* 运行:npx jest src/tests/viperFunctionHover.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 { 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 { HoverProvider } from '../languageService/hoverProvider';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgramForCode(code: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
|
||||
const fs = require('fs');
|
||||
const testDir = path.join(__dirname, '__hover__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'hover.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
return { program, configOptions, uri, testDir, serviceProvider };
|
||||
}
|
||||
|
||||
test('Viper hover 函数显示详尽调试信息', () => {
|
||||
const code = `import t, c
|
||||
|
||||
export def add[T](a: t.CInt, b: t.CInt = 10) -> t.CInt:
|
||||
return a + b
|
||||
|
||||
def use():
|
||||
add(1, 2)
|
||||
`;
|
||||
const { program, uri, serviceProvider } = buildProgramForCode(code);
|
||||
|
||||
// 悬停在调用点 `add(1, 2)` 的 `add` 上(L7,零基行 6,列 4)
|
||||
const position: Position = { line: 6, character: 4 };
|
||||
const provider = new HoverProvider(program as any, uri, position, MarkupKind.Markdown, CancellationToken.None);
|
||||
const hover = provider.getHover();
|
||||
assert(hover, 'hover result should not be null');
|
||||
const contents = (hover.contents as MarkupContent).value;
|
||||
console.log('=== hover 内容 ===');
|
||||
console.log(contents);
|
||||
|
||||
// 签名部分(原有 python 代码块)
|
||||
assert(contents.includes('(function)'), `should include signature: ${contents}`);
|
||||
// 调试信息标题
|
||||
assert(contents.includes('函数调试信息'), `should include debug heading: ${contents}`);
|
||||
// 定义位置(此文件:3:17 —— `add` 在 L3 第 17 列)
|
||||
assert(contents.includes('位置'), `should include 位置: ${contents}`);
|
||||
assert(contents.includes('此文件'), `should include 此文件: ${contents}`);
|
||||
// 修饰符
|
||||
assert(contents.includes('修饰符'), `should include 修饰符: ${contents}`);
|
||||
assert(contents.includes('export'), `should include export modifier: ${contents}`);
|
||||
// 泛型参数
|
||||
assert(contents.includes('泛型参数'), `should include 泛型参数: ${contents}`);
|
||||
assert(contents.includes('[T]'), `should include [T]: ${contents}`);
|
||||
// 参数
|
||||
assert(contents.includes('参数'), `should include 参数: ${contents}`);
|
||||
assert(contents.includes('a: CInt'), `should include param a: ${contents}`);
|
||||
assert(contents.includes('b: CInt = 10'), `should include param b with default: ${contents}`);
|
||||
// 返回类型
|
||||
assert(contents.includes('返回类型'), `should include 返回类型: ${contents}`);
|
||||
assert(contents.includes('CInt'), `should include return type: ${contents}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* viperNullZeroCast.test.ts
|
||||
*
|
||||
* 验证 Viper C 空指针常量(null pointer constant)规则:
|
||||
* - 整数字面量 0 可隐式转换为任意指针(NULL),包括赋值与函数传参
|
||||
* - 其他整数值(含值为 0 的变量)不允许隐式转换为指针,需显式转换
|
||||
* - None 仍允许(空指针)
|
||||
* - 显式转换 t.CInt(addr, t.CPtr) 仍允许
|
||||
*
|
||||
* 运行:npx jest src/tests/viperNullZeroCast.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgramForCode(code: string) {
|
||||
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, '__null_zero__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'null_zero.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
assert(sourceFile, 'source file not found');
|
||||
return { program, configOptions, uri, serviceProvider };
|
||||
}
|
||||
|
||||
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('整数字面量 0 → Ptr 隐式转换(NULL),其他整数拒绝', () => {
|
||||
// 注:带注解的变量声明初始化(p: T = v)遵循 Viper TypeSet 语义,
|
||||
// RHS 被声明类型覆盖不单独校验;本测试覆盖会真实触发赋值检查的
|
||||
// 重新赋值(p = v)与函数传参(foo(v))。
|
||||
const code = `import t, c
|
||||
|
||||
def takes_ptr(p: t.CInt | t.CPtr) -> t.CInt | t.CPtr:
|
||||
return p
|
||||
|
||||
def test_zero_cast():
|
||||
p: t.CInt | t.CPtr = None
|
||||
q: t.CInt | t.CPtr = None
|
||||
p = 0
|
||||
p = 1
|
||||
x: t.CInt = 0
|
||||
p = x
|
||||
takes_ptr(0)
|
||||
takes_ptr(1)
|
||||
addr: t.CUnsignedLong = 0x1234
|
||||
p = addr
|
||||
p = t.CInt(addr, t.CPtr)
|
||||
takes_ptr(p)
|
||||
takes_ptr(q)
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider } = buildProgramForCode(code);
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
|
||||
// 允许行:L7/L8 声明 None、L9 重新赋值 0、L13 函数传参 0、L17 显式转换
|
||||
for (const okLine of [7, 8, 9, 13, 17]) {
|
||||
const errs = diagnostics.filter((d) => d.includes(`[line=${okLine}]`));
|
||||
assert.strictEqual(
|
||||
errs.length,
|
||||
0,
|
||||
`L${okLine} 不应报错,实际: ${JSON.stringify(errs)}`
|
||||
);
|
||||
}
|
||||
|
||||
// 拒绝行:L10 重新赋值 1、L12 变量 x、L14 函数传参 1、L16 地址值变量
|
||||
for (const badLine of [10, 12, 14, 16]) {
|
||||
const errs = diagnostics.filter((d) => d.includes(`[line=${badLine}]`));
|
||||
assert(errs.length > 0, `L${badLine} 应报"仅允许整数字面量 0"错误,实际诊断: ${JSON.stringify(diagnostics)}`);
|
||||
const msg = errs[0];
|
||||
assert(
|
||||
msg.includes('仅允许整数字面量 0'),
|
||||
`L${badLine} 错误信息应提示仅允许 0,实际: ${msg}`
|
||||
);
|
||||
}
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* viperREnumKind.test.ts
|
||||
*
|
||||
* 验证 Viper REnum(t.REnum)自带 kind() 方法:
|
||||
* - REnum 内存布局为 { i32 __tag, <max_variant_payload> },
|
||||
* kind() 读取 __tag 判别值,返回 t.CInt(与 ast.AST.kind() 一致)。
|
||||
* - 所有 REnum 类实例(含 `MyEnum | t.CPtr` 折叠的 CPtr[MyEnum])均可
|
||||
* 访问 .kind 成员(解析为 `() -> int` 函数)并调用 .kind()(返回 int)。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperREnumKind.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 { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { CallNode, MemberAccessNode } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgramForCode(code: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
|
||||
const fs = require('fs');
|
||||
const testDir = path.join(__dirname, '__renum_kind__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'renum_kind.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}`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 语义 token 辅助:解码 SemanticTokens 相对编码,返回 line 级 token 映射。
|
||||
* 返回 Map<line, Array<{start, text, tokenType, tokenModifiers}>>
|
||||
*/
|
||||
function getSemanticTokens(program: Program, uri: any): Map<number, any[]> {
|
||||
const { SemanticTokenProvider } = require('../languageService/semanticTokenProvider');
|
||||
const { LogTracker } = require('../common/logTracker');
|
||||
const provider = new SemanticTokenProvider(new LogTracker());
|
||||
const result = provider.provideFull(program, uri);
|
||||
assert(result, 'no semantic tokens produced');
|
||||
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const lines = sourceFile!.getParseResults()!.text.split('\n');
|
||||
|
||||
const map = new Map<number, any[]>();
|
||||
let cursorLine = 0;
|
||||
let cursorChar = 0;
|
||||
for (let i = 0; i < result.data.length; i += 5) {
|
||||
const deltaLine = result.data[i];
|
||||
const deltaStart = result.data[i + 1];
|
||||
const length = result.data[i + 2];
|
||||
const tokenType = result.data[i + 3];
|
||||
const tokenMod = result.data[i + 4];
|
||||
cursorLine += deltaLine;
|
||||
cursorChar = deltaLine === 0 ? cursorChar + deltaStart : deltaStart;
|
||||
const lineText = lines[cursorLine] || '';
|
||||
const text = lineText.substring(cursorChar, cursorChar + length);
|
||||
const entry = { start: cursorChar, text, tokenType, tokenModifiers: tokenMod };
|
||||
if (!map.has(cursorLine)) {
|
||||
map.set(cursorLine, []);
|
||||
}
|
||||
map.get(cursorLine)!.push(entry);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
test('REnum 自带 kind() 返回 t.CInt(读取 __tag 判别值)', () => {
|
||||
const code = `import t, c
|
||||
|
||||
class MyEnum(t.REnum):
|
||||
class Int:
|
||||
Bits: t.CInt
|
||||
|
||||
class Ptr:
|
||||
Pointee: MyEnum | t.CPtr
|
||||
|
||||
def test_enum(e: MyEnum | t.CPtr) -> int:
|
||||
k: t.CInt = e.kind()
|
||||
k2: t.CInt = e.kind
|
||||
return k + k2
|
||||
|
||||
def test_assign() -> int:
|
||||
e: MyEnum | t.CPtr = MyEnum.Int(32)
|
||||
if e.kind() == 1:
|
||||
return 1
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const ev = evaluator;
|
||||
const lines = parseResults.parserOutput.lines;
|
||||
|
||||
// 收集 kind 成员访问与调用类型
|
||||
const memberTypes: { line: number; type: string }[] = [];
|
||||
const callTypes: { line: number; type: string }[] = [];
|
||||
class Collector extends ParseTreeWalker {
|
||||
override visitMemberAccess(node: MemberAccessNode) {
|
||||
const memberName = (node.d.member as any)?.d?.value ?? '?';
|
||||
if (memberName !== 'kind') {
|
||||
return true;
|
||||
}
|
||||
const pos = convertOffsetToPosition(node.start, lines);
|
||||
const t = ev.getType(node as any);
|
||||
memberTypes.push({
|
||||
line: pos.line + 1,
|
||||
type: t ? program.printType(t) : '(undefined)',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
override visitCall(node: CallNode) {
|
||||
const pos = convertOffsetToPosition(node.start, lines);
|
||||
const t = ev.getType(node as any);
|
||||
callTypes.push({
|
||||
line: pos.line + 1,
|
||||
type: t ? program.printType(t) : '(undefined)',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new Collector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
// 断言 1:e.kind 成员访问解析为函数 () -> int(L11/L12/L17)
|
||||
const kindMembers = memberTypes.filter((m) => m.type.includes('->'));
|
||||
console.log('=== .kind 成员类型 ===');
|
||||
memberTypes.forEach((m) => console.log(` L${m.line}: ${m.type}`));
|
||||
assert(kindMembers.length >= 3, `kind member should resolve to function type: ${JSON.stringify(memberTypes)}`);
|
||||
|
||||
// 断言 2:e.kind() 调用(L11/L17)返回 int
|
||||
const kindCalls = callTypes.filter((c) => c.line === 11 || c.line === 17);
|
||||
console.log('=== kind() 调用类型 ===');
|
||||
callTypes.forEach((c) => console.log(` L${c.line}: ${c.type}`));
|
||||
assert(kindCalls.length >= 2, `kind() calls not found: ${JSON.stringify(callTypes)}`);
|
||||
kindCalls.forEach((c) => {
|
||||
assert(c.type.includes('int'), `kind() should return int: ${JSON.stringify(c)}`);
|
||||
});
|
||||
|
||||
// 断言 3:不得有 category=0/1 错误(kind 不报 unknown member / not callable)
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const errors = diagnostics.filter((d) => d.includes('category=0') || d.includes('category=1'));
|
||||
console.log('=== 错误诊断 ===');
|
||||
errors.forEach((d) => console.log(` ${d}`));
|
||||
assert.strictEqual(errors.length, 0, `REnum kind should produce no errors: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
// 断言 4:kind 语义 token 应为 function(2,黄色),而非 variable(3)/property(13)
|
||||
const tokensByLine = getSemanticTokens(program, uri);
|
||||
for (const line of [11, 12, 17]) {
|
||||
const tokens = tokensByLine.get(line - 1);
|
||||
assert(tokens, `expected tokens on L${line}: ${JSON.stringify([...tokensByLine.keys()])}`);
|
||||
const kindToken = tokens.find((t) => t.text === 'kind');
|
||||
assert(kindToken, `expected "kind" token on L${line}: ${JSON.stringify(tokens)}`);
|
||||
assert.strictEqual(
|
||||
kindToken.tokenType,
|
||||
2,
|
||||
`"kind" on L${line} should be function(2), got ${kindToken.tokenType} (${JSON.stringify(kindToken)})`
|
||||
);
|
||||
}
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* viperRelImportNoInit.test.ts
|
||||
*
|
||||
* 验证 Viper 相对导入在目录无 __init__.vp 时的行为:
|
||||
* - `from . import X` 且目录中存在 X.vp → 正常解析,不报"命名空间包不完整"警告
|
||||
* - `from . import 不存在` → 仍报"命名空间包不完整"警告
|
||||
* - `import 空目录`(无 __init__.vp)→ 仍报"命名空间包不完整"警告
|
||||
*
|
||||
* 运行:npx jest src/tests/viperRelImportNoInit.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
function buildProgram(files: Record<string, string>) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__rel_import__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
for (const [relPath, content] of Object.entries(files)) {
|
||||
const absPath = path.join(testRoot, relPath);
|
||||
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
||||
fs.writeFileSync(absPath, content, '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 uris = Object.keys(files).map((relPath) => UriEx.file(path.join(testRoot, relPath)));
|
||||
const urisMap: Record<string, any> = {};
|
||||
for (const relPath of Object.keys(files)) {
|
||||
urisMap[relPath] = UriEx.file(path.join(testRoot, relPath));
|
||||
}
|
||||
program.setTrackedFiles(uris);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
return { program, configOptions, uris, urisMap, testRoot, 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('目录无 __init__.vp 时,from . import X(X.vp 存在)不报命名空间包警告', () => {
|
||||
const { program, configOptions, urisMap, testRoot, serviceProvider } = buildProgram({
|
||||
'Call/CallGenBase.vp': `def base_helper():
|
||||
return 0
|
||||
`,
|
||||
'Call/CallGenCBuiltin.vp': `from . import CallGenBase
|
||||
|
||||
def use_it() -> int:
|
||||
return CallGenBase.base_helper()
|
||||
`,
|
||||
'Call/other.vp': `from . import Nonexistent
|
||||
|
||||
def f():
|
||||
return Nonexistent
|
||||
`,
|
||||
'EmptyDir/.keep': ``,
|
||||
'main.vp': `import EmptyDir
|
||||
|
||||
def main():
|
||||
return 0
|
||||
`,
|
||||
});
|
||||
|
||||
// 1. from . import CallGenBase(CallGenBase.vp 存在)→ 不应报"命名空间包不完整"
|
||||
const callGenDiags = getDiagnostics(program, configOptions, urisMap['Call/CallGenCBuiltin.vp']);
|
||||
assert(
|
||||
!callGenDiags.some((d) => d.includes('缺少 __init__.vp')),
|
||||
`from . import CallGenBase 不应报命名空间包警告,实际: ${JSON.stringify(callGenDiags)}`
|
||||
);
|
||||
|
||||
// 2. from . import Nonexistent(文件不存在)→ 解析为"未找到"(非命名空间包),
|
||||
// 不产生命名空间包警告;Nonexistent 使用处报未定义(证明导入确实失败)
|
||||
const otherDiags = getDiagnostics(program, configOptions, urisMap['Call/other.vp']);
|
||||
assert(
|
||||
!otherDiags.some((d) => d.includes('缺少 __init__.vp')),
|
||||
`from . import Nonexistent 不应报命名空间包警告,实际: ${JSON.stringify(otherDiags)}`
|
||||
);
|
||||
assert(
|
||||
otherDiags.length > 0,
|
||||
`from . import Nonexistent 应产生未定义/类型错误,实际: ${JSON.stringify(otherDiags)}`
|
||||
);
|
||||
|
||||
// 3. import EmptyDir(目录无 __init__.vp)→ 仍报"命名空间包不完整"
|
||||
const mainDiags = getDiagnostics(program, configOptions, urisMap['main.vp']);
|
||||
assert(
|
||||
mainDiags.some((d) => d.includes('缺少 __init__.vp')),
|
||||
`import EmptyDir 应报命名空间包警告,实际: ${JSON.stringify(mainDiags)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testRoot, { recursive: true, force: true });
|
||||
});
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* viperTyNarrowDump.test.ts — 临时复现 target_alloca.Ty 收窄为 CPtr[None]
|
||||
*/
|
||||
|
||||
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 { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { MemberAccessNode } from '../parser/parseNodes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
const TPV_APP = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\TransPyV\\App';
|
||||
const ASSIGN_GEN =
|
||||
'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\TransPyV\\App\\lib\\core\\Gen\\AssignGenMixin.vp';
|
||||
|
||||
function buildProgram(targetPath: string, extraPaths: string[]) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(path.dirname(targetPath));
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot, ...extraPaths.map((p) => UriEx.file(p))],
|
||||
/* 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(targetPath);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const parseResults = sourceFile!.getParseResults();
|
||||
return { program, configOptions, parseResults, uri, serviceProvider };
|
||||
}
|
||||
|
||||
test('AssignGenMixin.vp 守卫后 .Ty/.Pointee 不误收窄为 CPtr[None]', () => {
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(ASSIGN_GEN, [
|
||||
TRANSPYC_INCLUDES,
|
||||
TPV_APP,
|
||||
]);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
const out: string[] = [];
|
||||
if (evaluator && parseResults) {
|
||||
const ev = evaluator;
|
||||
const lines = parseResults.parserOutput.lines;
|
||||
const pointeeTypes: string[] = [];
|
||||
const tyTypes: string[] = [];
|
||||
class TyCollector extends ParseTreeWalker {
|
||||
override visitMemberAccess(node: MemberAccessNode) {
|
||||
const pos = convertOffsetToPosition(node.start, lines);
|
||||
if (pos.line < 795 || pos.line > 830) {
|
||||
return true;
|
||||
}
|
||||
const memberName = (node.d.member as any)?.d?.value ?? '?';
|
||||
const t = ev.getType(node as any);
|
||||
const printed = t ? program.printType(t) : '(undefined)';
|
||||
if (memberName === 'Pointee') {
|
||||
pointeeTypes.push(printed);
|
||||
} else if (memberName === 'Ty') {
|
||||
tyTypes.push(printed);
|
||||
}
|
||||
out.push(` L${pos.line + 1} .${memberName}: ${printed}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new TyCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
// 核心断言 1:`if target_alloca.Ty is None:` 守卫之后,.Ty 不再被误窄化为 CPtr[None]
|
||||
//(None 是空指针取值,is None 正测试不改变 CPtr 指针类型本身)
|
||||
expect(tyTypes.some((s) => s.includes('[None]'))).toBe(false);
|
||||
expect(tyTypes.every((s) => s === 'CPtr[LLVMType]')).toBe(true);
|
||||
|
||||
// 核心断言 2:守卫之后 .Pointee 可正常解析(原报"无法访问类'None'的属性'Pointee'")
|
||||
expect(pointeeTypes.some((s) => s === 'Unknown')).toBe(false);
|
||||
expect(pointeeTypes.every((s) => s === 'CPtr[LLVMType]')).toBe(true);
|
||||
|
||||
// 核心断言 3:L795-830 范围内不应再出现"无法访问/未定义"诊断
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const diags = sourceFile!.getDiagnostics(configOptions) || [];
|
||||
const rangeDiags = diags.filter((d: any) => {
|
||||
const line = d.range ? d.range.start.line + 1 : -1;
|
||||
return line >= 795 && line <= 830;
|
||||
});
|
||||
expect(rangeDiags.length).toBe(0);
|
||||
rangeDiags.forEach((d: any) => {
|
||||
out.push(` [category=${d.category}][line=${(d.range?.start.line ?? -1) + 1}] ${d.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
require('fs').writeFileSync(path.join(__dirname, '__ty_narrow_dump.txt'), out.join('\n') + '\n', 'utf8');
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
test('AssignGenMixin.vp match 变体模式内 rhs_val.Ty 保持指针形态 CPtr[Struct]', () => {
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(ASSIGN_GEN, [
|
||||
TRANSPYC_INCLUDES,
|
||||
TPV_APP,
|
||||
]);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
const out: string[] = [];
|
||||
if (evaluator && parseResults) {
|
||||
const ev = evaluator;
|
||||
const lines = parseResults.parserOutput.lines;
|
||||
const tyByLine: Array<{ line: number; type: string }> = [];
|
||||
class TyCollector extends ParseTreeWalker {
|
||||
override visitMemberAccess(node: MemberAccessNode) {
|
||||
const pos = convertOffsetToPosition(node.start, lines);
|
||||
if (pos.line < 1525 || pos.line > 1560) {
|
||||
return true;
|
||||
}
|
||||
const memberName = (node.d.member as any)?.d?.value ?? '?';
|
||||
const t = ev.getType(node as any);
|
||||
if (memberName === 'Ty') {
|
||||
tyByLine.push({ line: pos.line + 1, type: t ? program.printType(t) : '(undefined)' });
|
||||
}
|
||||
out.push(` L${pos.line + 1} .${memberName}: ${t ? program.printType(t) : '(undefined)'}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new TyCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
// 核心断言 1:Struct case 体内(L1537/L1540/L1559)的 .Ty 保持指针形态 CPtr[Struct],
|
||||
// 而非误收窄为值类型 Struct(原导致 build_alloca 等报"Struct 不可分配给 CPtr[LLVMType]")
|
||||
const caseBodyTy = tyByLine.filter((e) => e.line >= 1537);
|
||||
expect(caseBodyTy.length).toBeGreaterThan(0);
|
||||
expect(caseBodyTy.every((e) => e.type === 'CPtr[Struct]')).toBe(true);
|
||||
// 全范围不允许出现纯值类型 Struct
|
||||
expect(tyByLine.some((e) => e.type === 'Struct')).toBe(false);
|
||||
|
||||
// 核心断言 2:L1525-1560 范围内不应再出现"不可分配给"诊断
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
const diags = sourceFile!.getDiagnostics(configOptions) || [];
|
||||
const rangeDiags = diags.filter((d: any) => {
|
||||
const line = d.range ? d.range.start.line + 1 : -1;
|
||||
return line >= 1525 && line <= 1560;
|
||||
});
|
||||
expect(rangeDiags.length).toBe(0);
|
||||
rangeDiags.forEach((d: any) => {
|
||||
out.push(` [category=${d.category}][line=${(d.range?.start.line ?? -1) + 1}] ${d.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
require('fs').writeFileSync(path.join(__dirname, '__ty_narrow_dump.txt'), out.join('\n') + '\n', 'utf8');
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* viperUnaryPointer.test.ts
|
||||
*
|
||||
* 验证 Viper 一元 '*'(解引用)与 '&'(取地址)运算符,与 C 语义一致:
|
||||
* - `&x`:取地址,返回指向 x 的指针(`X | t.CPtr` 化简为 CPtr[X])
|
||||
* - `*p`:解引用,返回 p 指向的目标类型
|
||||
* - 非指针解引用报错
|
||||
*
|
||||
* 以及 C 指针类型注解语法(Viper 方言):
|
||||
* - `p: T*` → CPtr[T]
|
||||
* - `p: T**` → CPtr[CPtr[T]]
|
||||
* - `p: T***` → CPtr[CPtr[CPtr[T]]]
|
||||
*
|
||||
* 运行:npx jest src/tests/viperUnaryPointer.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 { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { FunctionNode, ParameterNode, TypeAnnotationNode, UnaryOperationNode } from '../parser/parseNodes';
|
||||
import { OperatorType } from '../parser/tokenizerTypes';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgramForCode(code: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
|
||||
const fs = require('fs');
|
||||
const testDir = path.join(__dirname, '__unary_ptr__');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'unary_ptr.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, testDir, 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('Viper 一元 * 解引用 / & 取地址 类型求值', () => {
|
||||
const code = `import t, c
|
||||
|
||||
class MyStruct():
|
||||
a: t.CInt
|
||||
b: t.CInt
|
||||
|
||||
def test_addr(x: t.CInt):
|
||||
# &x 返回指向 x 的指针:CPtr[CInt]
|
||||
p: t.CInt | t.CPtr = &x
|
||||
y: t.CInt = *p
|
||||
|
||||
def test_struct_ptr(s: MyStruct):
|
||||
# &s 返回指向 MyStruct 的指针,解引用后可访问成员
|
||||
p: MyStruct | t.CPtr = &s
|
||||
a: t.CInt = (*p).a
|
||||
b: t.CInt = p.a
|
||||
|
||||
def test_bad_deref(x: t.CInt):
|
||||
# 非指针解引用:应报错
|
||||
y: t.CInt = *x
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, testDir, serviceProvider } = buildProgramForCode(code);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const ev = evaluator;
|
||||
|
||||
// 收集所有一元运算节点(* / &)的类型
|
||||
const unaryOps: { line: number; op: string; type: string }[] = [];
|
||||
class UnaryCollector extends ParseTreeWalker {
|
||||
override visitUnaryOperation(node: UnaryOperationNode) {
|
||||
if (node.d.operator !== OperatorType.Multiply && node.d.operator !== OperatorType.BitwiseAnd) {
|
||||
return true;
|
||||
}
|
||||
const op = node.d.operator === OperatorType.Multiply ? '*' : '&';
|
||||
const type = ev.getType(node as any);
|
||||
unaryOps.push({
|
||||
line: convertOffsetToPosition(node.start, parseResults.parserOutput.lines).line + 1,
|
||||
op,
|
||||
type: type ? program.printType(type) : '(undefined)',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new UnaryCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
console.log('=== 一元 * / & 节点类型 ===');
|
||||
unaryOps.forEach((u) => console.log(` L${u.line} ${u.op}: ${u.type}`));
|
||||
|
||||
// 断言 1:&x 返回 CPtr[CInt](L9 p 的 RHS)
|
||||
const addrOf = unaryOps.find((u) => u.op === '&' && u.line === 9);
|
||||
assert(addrOf, '&x not found (L9)');
|
||||
assert(addrOf.type.includes('CInt'), `&x type should contain CInt: ${addrOf.type}`);
|
||||
|
||||
// 断言 2:*p 解引用返回 CInt(L10)
|
||||
const derefL10 = unaryOps.find((u) => u.op === '*' && u.line === 10);
|
||||
assert(derefL10, '*p not found (L10)');
|
||||
assert(derefL10.type.includes('CInt'), `*p should resolve to CInt: ${derefL10.type}`);
|
||||
|
||||
// 断言 3:(*p).a 解引用结构体(L15)
|
||||
const derefL15 = unaryOps.find((u) => u.op === '*' && u.line === 15);
|
||||
assert(derefL15, '(*p) not found (L15)');
|
||||
assert(derefL15.type.includes('MyStruct'), `(*p) should be MyStruct: ${derefL15.type}`);
|
||||
|
||||
// 断言 4:非指针解引用 *x 报错(L20)
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const badDerefErrors = diagnostics.filter((d) => d.includes('line=20') && d.includes('*'));
|
||||
console.log('=== L20 诊断 ===');
|
||||
diagnostics.filter((d) => d.includes('line=20')).forEach((d) => console.log(` ${d}`));
|
||||
assert(badDerefErrors.length > 0, `*x (non-pointer) should report error: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
test('Viper 指针类型注解 T* / T** / T*** 类型求值', () => {
|
||||
const code = `import t, c
|
||||
|
||||
class MyStruct():
|
||||
a: t.CInt
|
||||
b: t.CInt
|
||||
|
||||
p1: t.CInt*
|
||||
p2: t.CInt**
|
||||
p3: t.CInt***
|
||||
|
||||
sp: MyStruct*
|
||||
v: t.CInt = sp.a
|
||||
|
||||
def test_interop(x: t.CInt):
|
||||
q: t.CInt* = &x
|
||||
v: t.CInt = *q
|
||||
|
||||
def test_double_deref(pp: t.CInt**):
|
||||
v: t.CInt = *(*pp)
|
||||
|
||||
def make_ptr(x: t.CInt) -> t.CInt*:
|
||||
return &x
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const ev = evaluator;
|
||||
|
||||
// 收集所有类型注解(变量注解 / 参数注解 / 返回类型)的类型
|
||||
const anns: { line: number; type: string }[] = [];
|
||||
const collect = (node: any) => {
|
||||
const t = ev.getTypeOfAnnotation(node);
|
||||
anns.push({
|
||||
line: convertOffsetToPosition(node.start, parseResults.parserOutput.lines).line + 1,
|
||||
type: t ? program.printType(t) : '(undefined)',
|
||||
});
|
||||
};
|
||||
class AnnCollector extends ParseTreeWalker {
|
||||
override visitTypeAnnotation(node: TypeAnnotationNode) {
|
||||
collect(node.d.annotation);
|
||||
return true;
|
||||
}
|
||||
override visitParameter(node: ParameterNode) {
|
||||
if (node.d.annotation) {
|
||||
collect(node.d.annotation);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
override visitFunction(node: FunctionNode) {
|
||||
if (node.d.returnAnnotation) {
|
||||
collect(node.d.returnAnnotation);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new AnnCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
console.log('=== 指针类型注解类型 ===');
|
||||
anns.forEach((a) => console.log(` L${a.line}: ${a.type}`));
|
||||
|
||||
// T* → CPtr[CInt](L7)
|
||||
const p1 = anns.find((a) => a.line === 7);
|
||||
assert(p1, 'p1 annotation not found (L7)');
|
||||
assert(p1.type.includes('CPtr[CInt]'), `p1 should be CPtr[CInt]: ${p1.type}`);
|
||||
|
||||
// T** → CPtr[CPtr[CInt]](L8)
|
||||
const p2 = anns.find((a) => a.line === 8);
|
||||
assert(p2, 'p2 annotation not found (L8)');
|
||||
assert(p2.type.includes('CPtr[CPtr[CInt]]'), `p2 should be CPtr[CPtr[CInt]]: ${p2.type}`);
|
||||
|
||||
// T*** → CPtr[CPtr[CPtr[CInt]]](L9)
|
||||
const p3 = anns.find((a) => a.line === 9);
|
||||
assert(p3, 'p3 annotation not found (L9)');
|
||||
assert(p3.type.includes('CPtr[CPtr[CPtr[CInt]]]'), `p3 should be CPtr[CPtr[CPtr[CInt]]]: ${p3.type}`);
|
||||
|
||||
// 结构体指针 MyStruct* → CPtr[MyStruct](L11)
|
||||
const sp = anns.find((a) => a.line === 11);
|
||||
assert(sp, 'sp annotation not found (L11)');
|
||||
assert(sp.type.includes('CPtr[MyStruct]'), `sp should be CPtr[MyStruct]: ${sp.type}`);
|
||||
|
||||
// 参数 q: t.CInt*(L15)
|
||||
const q = anns.find((a) => a.line === 15);
|
||||
assert(q, 'q annotation not found (L15)');
|
||||
assert(q.type.includes('CPtr[CInt]'), `q should be CPtr[CInt]: ${q.type}`);
|
||||
|
||||
// 参数 pp: t.CInt**(L18)
|
||||
const pp = anns.find((a) => a.line === 18);
|
||||
assert(pp, 'pp annotation not found (L18)');
|
||||
assert(pp.type.includes('CPtr[CPtr[CInt]]'), `pp should be CPtr[CPtr[CInt]]: ${pp.type}`);
|
||||
|
||||
// 返回类型 -> t.CInt*(L21)
|
||||
const ret = anns.find((a) => a.line === 21);
|
||||
assert(ret, 'return annotation not found (L21)');
|
||||
assert(ret.type.includes('CPtr[CInt]'), `return should be CPtr[CInt]: ${ret.type}`);
|
||||
|
||||
// 不得有任何 Error 类别诊断(指针类型注解不应产生错误)
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const errors = diagnostics.filter((d) => d.includes('category=0'));
|
||||
console.log('=== 错误诊断 ===');
|
||||
errors.forEach((d) => console.log(` ${d}`));
|
||||
assert(errors.length === 0, `pointer type annotations should produce no errors: ${JSON.stringify(errors)}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
test('Viper 无空格 T* = 赋值:词法 `*=`/`**=` 拆分为指针后缀 + 赋值号', () => {
|
||||
const code = `import t, c
|
||||
|
||||
def main() -> int:
|
||||
x: int = 42
|
||||
px: int*=&x
|
||||
ppx: int**=&px
|
||||
d: int = *px
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const ev = evaluator;
|
||||
|
||||
const anns: { line: number; type: string }[] = [];
|
||||
class AnnCollector extends ParseTreeWalker {
|
||||
override visitTypeAnnotation(node: TypeAnnotationNode) {
|
||||
const t = ev.getTypeOfAnnotation(node.d.annotation);
|
||||
anns.push({
|
||||
line: convertOffsetToPosition(node.d.annotation.start, parseResults.parserOutput.lines).line + 1,
|
||||
type: t ? program.printType(t) : '(undefined)',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new AnnCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
console.log('=== 无空格 *=/ **= 注解类型 ===');
|
||||
anns.forEach((a) => console.log(` L${a.line}: ${a.type}`));
|
||||
|
||||
// L5: px: int*=&x → CPtr[int];L6: ppx: int**=&px → CPtr[CPtr[int]]
|
||||
const px = anns.find((a) => a.line === 5);
|
||||
assert(px, 'px annotation not found (L5)');
|
||||
assert(px.type.includes('CPtr[int]'), `px should be CPtr[int]: ${px.type}`);
|
||||
const ppx = anns.find((a) => a.line === 6);
|
||||
assert(ppx, 'ppx annotation not found (L6)');
|
||||
assert(ppx.type.includes('CPtr[CPtr[int]]'), `ppx should be CPtr[CPtr[int]]: ${ppx.type}`);
|
||||
|
||||
// 不得有 Error 类别诊断(含内部错误)
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const errors = diagnostics.filter((d) => d.includes('category=0'));
|
||||
console.log('=== 错误诊断 ===');
|
||||
errors.forEach((d) => console.log(` ${d}`));
|
||||
assert(errors.length === 0, `no-space star-assign should produce no errors: ${JSON.stringify(errors)}`);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
test('Viper 解引用赋值:*p = value(联合形式与 T* 形式)', () => {
|
||||
const code = `import t, c
|
||||
|
||||
def main() -> int:
|
||||
x: int = 42
|
||||
px: int | t.CPtr = &x
|
||||
*px = 100
|
||||
y: int = *px
|
||||
pz: int* = &x
|
||||
*pz = 7
|
||||
return 0
|
||||
`;
|
||||
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
|
||||
|
||||
const evaluator = program.evaluator as TypeEvaluator | undefined;
|
||||
assert(evaluator, 'evaluator not available');
|
||||
const ev = evaluator;
|
||||
|
||||
const anns: { line: number; type: string }[] = [];
|
||||
class AnnCollector extends ParseTreeWalker {
|
||||
override visitTypeAnnotation(node: TypeAnnotationNode) {
|
||||
const t = ev.getTypeOfAnnotation(node.d.annotation);
|
||||
anns.push({
|
||||
line: convertOffsetToPosition(node.d.annotation.start, parseResults.parserOutput.lines).line + 1,
|
||||
type: t ? program.printType(t) : '(undefined)',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new AnnCollector().walk(parseResults.parserOutput.parseTree);
|
||||
|
||||
console.log('=== 解引用赋值 注解类型 ===');
|
||||
anns.forEach((a) => console.log(` L${a.line}: ${a.type}`));
|
||||
|
||||
// L5: px: int | t.CPtr → CPtr[int](联合折叠);L8: pz: int* → CPtr[int]
|
||||
const px = anns.find((a) => a.line === 5);
|
||||
assert(px, 'px annotation not found (L5)');
|
||||
assert(px.type.includes('CPtr[int]'), `px should fold to CPtr[int]: ${px.type}`);
|
||||
const pz = anns.find((a) => a.line === 8);
|
||||
assert(pz, 'pz annotation not found (L8)');
|
||||
assert(pz.type.includes('CPtr[int]'), `pz should be CPtr[int]: ${pz.type}`);
|
||||
|
||||
// *px = 100(L6)与 *pz = 7(L8)不得报"表达式不能是赋值目标";
|
||||
// y: int = *px 解引用右值赋值兼容。
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
const errors = diagnostics.filter((d) => d.includes('category=0'));
|
||||
console.log('=== 错误诊断 ===');
|
||||
errors.forEach((d) => console.log(` ${d}`));
|
||||
assert(errors.length === 0, `deref assignment should produce no errors: ${JSON.stringify(errors)}`);
|
||||
assert(
|
||||
!diagnostics.some((d) => d.includes('赋值目标')),
|
||||
`deref should not report assignment target error: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* viperUnknownMemberAccess.test.ts
|
||||
*
|
||||
* 验证 Viper 的"对象可能未定义"(viperUnknownMemberAccess)诊断:
|
||||
* - 仅对"最终" Unknown(isIncomplete=false,如 import 解析失败)报错;
|
||||
* - 对分析中间态 Unknown(isIncomplete=true,如循环 import 中方法 self
|
||||
* 绑定尚未收敛)不报错,避免误报。
|
||||
*
|
||||
* 回归场景:TransPyC StructGenMixin.get_mro_chain(L1358)内 `self.find_struct(cur)`,
|
||||
* 其 self 参数在循环 import(StructGenMixin ⇄ Generator)分析早期为
|
||||
* Unknown(incomplete),收敛后为 Self@StructGenMixin。含 App 搜索根时不得报错;
|
||||
* 缺 App 搜索根(Generator.vp import 失败)时 `self.Gen.StructGen.find_struct`
|
||||
* 应正常报"对象可能未定义"。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperUnknownMemberAccess.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
const TPV_APP = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\TransPyV\\App';
|
||||
const STRUCT_GEN_MIXIN =
|
||||
'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\TransPyV\\App\\lib\\core\\Gen\\StructGenMixin.vp';
|
||||
const ASSIGN_GEN_MIXIN =
|
||||
'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\TransPyV\\App\\lib\\core\\Gen\\AssignGenMixin.vp';
|
||||
|
||||
function buildProgram(targetPath: string, extraPaths: string[]) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(path.dirname(targetPath));
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot, ...extraPaths.map((p) => UriEx.file(p))],
|
||||
/* 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(targetPath);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
return { program, configOptions, uri, serviceProvider };
|
||||
}
|
||||
|
||||
function getFindStructDiags(program: Program, configOptions: ConfigOptions, uri: any): string[] {
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
assert(sourceFile, 'source file not found');
|
||||
return (sourceFile.getDiagnostics(configOptions) || [])
|
||||
.filter((d) => d.message.includes('find_struct'))
|
||||
.map((d) => {
|
||||
const line = d.range ? d.range.start.line + 1 : -1;
|
||||
return `[category=${d.category}][line=${line}] ${d.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
test('循环 import 中间态 Unknown(isIncomplete)不报"对象可能未定义"', () => {
|
||||
// 含 App 搜索根:Generator.vp import 全部解析成功,self 收敛为 Self@StructGenMixin,
|
||||
// get_mro_chain 内 self.find_struct(L1358)不得误报。
|
||||
const { program, configOptions, uri, serviceProvider } = buildProgram(STRUCT_GEN_MIXIN, [
|
||||
TRANSPYC_INCLUDES,
|
||||
TPV_APP,
|
||||
]);
|
||||
|
||||
const findStructDiags = getFindStructDiags(program, configOptions, uri);
|
||||
console.log('=== 含 App 根: find_struct 诊断 ===');
|
||||
findStructDiags.forEach((d) => console.log(` ${d}`));
|
||||
assert.strictEqual(
|
||||
findStructDiags.length,
|
||||
0,
|
||||
`self.find_struct should not report on incomplete Unknown: ${JSON.stringify(findStructDiags)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
test('import 解析失败的最终 Unknown 仍报"对象可能未定义"', () => {
|
||||
// 缺 App 搜索根:Generator.vp 的 lib.core.* import 失败 → self.Gen 链为最终
|
||||
// Unknown(isIncomplete=false)→ self.Gen.StructGen.find_struct 应报错(真实问题)。
|
||||
const { program, configOptions, uri, serviceProvider } = buildProgram(ASSIGN_GEN_MIXIN, [TRANSPYC_INCLUDES]);
|
||||
|
||||
const findStructDiags = getFindStructDiags(program, configOptions, uri);
|
||||
console.log('=== 缺 App 根: find_struct 诊断 ===');
|
||||
findStructDiags.forEach((d) => console.log(` ${d}`));
|
||||
assert(
|
||||
findStructDiags.length >= 3,
|
||||
`final Unknown should report find_struct errors: ${JSON.stringify(findStructDiags)}`
|
||||
);
|
||||
assert(
|
||||
findStructDiags.every((d) => d.includes('对象可能未定义')),
|
||||
`find_struct errors should be viperUnknownMemberAccess: ${JSON.stringify(findStructDiags)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* viperVpnci.test.ts
|
||||
*
|
||||
* 验证 Viper 不可编译注解文件 `.vpnci` 与 `.vpi` 配置完全一致:
|
||||
* - `import MyModule` 可解析到 MyModule.vpnci(不报"导入失败/无法解析")
|
||||
* - .vpnci 文件被识别为 stub 文件(isStubFile)
|
||||
* - from 导入其中的声明可正常引用
|
||||
*
|
||||
* 运行:npx jest src/tests/viperVpnci.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
function buildProgram(files: Record<string, string>) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const fs = require('fs');
|
||||
const testRoot = path.join(__dirname, '__vpnci__');
|
||||
fs.rmSync(testRoot, { recursive: true, force: true });
|
||||
for (const [relPath, content] of Object.entries(files)) {
|
||||
const absPath = path.join(testRoot, relPath);
|
||||
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
||||
fs.writeFileSync(absPath, content, '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 uris = Object.keys(files).map((relPath) => UriEx.file(path.join(testRoot, relPath)));
|
||||
const urisMap: Record<string, any> = {};
|
||||
for (const relPath of Object.keys(files)) {
|
||||
urisMap[relPath] = UriEx.file(path.join(testRoot, relPath));
|
||||
}
|
||||
program.setTrackedFiles(uris);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
return { program, configOptions, urisMap, testRoot, 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('.vpnci 不可编译注解文件:import 解析成功、识别为 stub、声明可引用', () => {
|
||||
const { program, configOptions, urisMap, testRoot, serviceProvider } = buildProgram({
|
||||
'MyModule.vpnci': `def helper() -> int:
|
||||
...
|
||||
`,
|
||||
'main.vp': `from MyModule import helper
|
||||
import MyModule
|
||||
|
||||
def main() -> int:
|
||||
return helper() + MyModule.helper()
|
||||
`,
|
||||
});
|
||||
|
||||
// 1. main.vp 中 import/from 均解析成功,无"无法解析"或未定义
|
||||
const mainDiags = getDiagnostics(program, configOptions, urisMap['main.vp']);
|
||||
assert(
|
||||
!mainDiags.some((d) => d.includes('无法解析') || d.includes('could not be resolved') || d.includes('未定义')),
|
||||
`import MyModule 应解析成功且 helper 可引用,实际: ${JSON.stringify(mainDiags)}`
|
||||
);
|
||||
|
||||
// 2. .vpnci 文件被识别为 stub 文件(与 .vpi 一致)
|
||||
const stubFile = program.getSourceFile(urisMap['MyModule.vpnci']);
|
||||
assert(stubFile, 'MyModule.vpnci 应被程序索引');
|
||||
assert(stubFile.isStubFile(), 'MyModule.vpnci 应被识别为 stub 文件');
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testRoot, { recursive: true, force: true });
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import _ast
|
||||
import _sitebuiltins
|
||||
import _typeshed
|
||||
import sys
|
||||
import t
|
||||
import types
|
||||
from _collections_abc import dict_items, dict_keys, dict_values
|
||||
from _typeshed import (
|
||||
@@ -2661,4 +2662,13 @@ def LInp(expr: Any, constraint: Any = None) -> Any:
|
||||
|
||||
def LOut(expr: Any, constraint: Any = None) -> Any:
|
||||
"""LLVM IR 输出操作数标记(等价 c.LOut)。"""
|
||||
...
|
||||
...
|
||||
|
||||
# --- Viper 扩展:编译器内置裸指针类型 rowptr ---
|
||||
# rowptr 是编译器内置类型(TPC/TPV 双端,语法保留字,不可重定义):
|
||||
# rowptr[T] 固定表示"裸指针,指向原始类型 T"(等价 t.CPtr[T]),
|
||||
# 单独 rowptr 等价 t.CPtr(void*)。
|
||||
# 声明在 builtins stub 中使 LSP 对任意 Viper 工程均可解析 rowptr,不依赖
|
||||
# 项目 includes/builtins.vp 是否声明;编译器侧解析 rowptr 时优先走内置
|
||||
# 路径并忽略本声明(见 TransPyC TypeAnnotationResolver / SymbolExtractor)。
|
||||
rowptr: t.CTypedef = t.CPtr
|
||||
@@ -38,7 +38,8 @@
|
||||
],
|
||||
"extensions": [
|
||||
".vp",
|
||||
".vpi"
|
||||
".vpi",
|
||||
".vpnci"
|
||||
],
|
||||
"configuration": "./language-configuration.json",
|
||||
"icon": {
|
||||
@@ -152,6 +153,9 @@
|
||||
"entity.name.function.viper",
|
||||
"entity.name.function"
|
||||
],
|
||||
"macro": [
|
||||
"storage.type.function.viper"
|
||||
],
|
||||
"class": [
|
||||
"entity.name.class",
|
||||
"entity.name.type"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.control.flow.viper",
|
||||
"match": "\\b(if|elif|else|while|for|in|return|break|continue|pass|raise|try|except|finally|with|as|import|from|yield|async|await|del|assert|lambda|match|case)\\b"
|
||||
"match": "\\b(if|elif|else|while|for|in|return|break|continue|pass|raise|try|except|finally|with|as|import|from|yield|async|await|del|assert|lambda|match|case|define)\\b"
|
||||
},
|
||||
{
|
||||
"name": "storage.type.function.viper",
|
||||
|
||||
Reference in new issue
Block a user