feat(vipeye-internal): 类型展示保留源码书写的完整 module 路径

- typePrinter 新增 ViperSourceQualifiedNames(1<<15),打印类名时优先取
  ClassDetailsPriv.displayName(源码里实际书写的 ast.AST 形式),未写限定路径的
  裸名(list/str)保持短名;UniqueNameMap 与打印共用 getPrintableClassName,
  避免名字唯一性判定与实际打印名不一致导致误降级
- types 新增 displayName 字段与 cloneForDisplayName(仅用于展示,不参与类型判定)
- typeEvaluator 在成员访问处记录点号路径(getDottedNamePath),仅 Viper 方言生效
- hoverProvider / tooltipUtils 透传 PrintTypeOptions.sourceQualifiedNames
- 修正 hover 与补全的代码围栏注释:```viper 实际可着色,但只有通用 scope 能命中
  主题规则(def/class 的 storage.type.*.viper 与按 Python 精调的主题规则不匹配),
  故继续使用 ```python
- 同步 viperUnaryPointer / viperFunctionHover 用例的展示断言
This commit is contained in:
TermiNexus committed 2026-09-19 17:58:49 +08:00
1 parent c9acd4ecb8
commit cfccbac6f4
10 files changed
+233 -45

No files matched your search

@@ -6186,12 +6186,58 @@ export function createTypeEvaluator(
);
}
// Viper 扩展:记录源码中书写的限定路径(如 `ast.AST`),供类型展示保留完整
// module 路径(见 typePrinter 的 ViperSourceQualifiedNames)。
typeResult.type = addViperDisplayName(node, typeResult.type);
// Cache the type information in the member name node.
writeTypeCache(node.d.member, typeResult, flags);
return typeResult;
}
// Viper 扩展:把成员访问链在源码中书写的完整路径(如 `ast.AST`、`memhub.MemBuddy`)
// 记到类型上,仅供展示使用(ClassDetailsPriv.displayName,不参与任何类型判定)。
// 只处理纯点号路径(Name/MemberAccess 组成,不含调用/下标),且结果必须是类类型;
// 其它情况原样返回。
function addViperDisplayName(node: MemberAccessNode, type: Type): Type {
if (!isClassInstance(type) && !isInstantiableClass(type)) {
return type;
}
if (!IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect)) {
return type;
}
const path = getDottedNamePath(node);
if (!path) {
return type;
}
return ClassType.cloneForDisplayName(type, path);
}
// 把 `a.b.c` 形式的访问链还原为源码路径文本;链上出现非 Name/MemberAccess
// 节点(调用、下标等)时返回 undefined。
function getDottedNamePath(node: ExpressionNode): string | undefined {
const nameParts: string[] = [];
let curNode: ExpressionNode = node;
while (true) {
if (curNode.nodeType === ParseNodeType.MemberAccess) {
nameParts.push(curNode.d.member.d.value);
curNode = curNode.d.leftExpr;
} else if (curNode.nodeType === ParseNodeType.Name) {
nameParts.push(curNode.d.value);
break;
} else {
return undefined;
}
}
return nameParts.reverse().join('.');
}
function getTypeOfMemberAccessWithBaseType(
node: MemberAccessNode,
baseTypeResult: TypeResult,
@@ -30865,8 +30911,15 @@ export function createTypeEvaluator(
return TypePrinter.printObjectTypeForClass(type, evaluatorOptions.printTypeFlags, getEffectiveReturnType);
}
function printFunctionParts(type: FunctionType, extraFlags?: TypePrinter.PrintTypeFlags): [string[], string] {
const flags = extraFlags ? evaluatorOptions.printTypeFlags | extraFlags : evaluatorOptions.printTypeFlags;
function printFunctionParts(
type: FunctionType,
extraFlags?: TypePrinter.PrintTypeFlags,
options?: PrintTypeOptions
): [string[], string] {
let flags = extraFlags ? evaluatorOptions.printTypeFlags | extraFlags : evaluatorOptions.printTypeFlags;
if (options?.sourceQualifiedNames) {
flags |= TypePrinter.PrintTypeFlags.ViperSourceQualifiedNames;
}
return TypePrinter.printFunctionParts(type, flags, getEffectiveReturnType);
}
@@ -30918,6 +30971,9 @@ export function createTypeEvaluator(
if (options?.useFullyQualifiedNames) {
flags |= TypePrinter.PrintTypeFlags.UseFullyQualifiedNames;
}
if (options?.sourceQualifiedNames) {
flags |= TypePrinter.PrintTypeFlags.ViperSourceQualifiedNames;
}
if (options?.disablePep604) {
flags &= ~TypePrinter.PrintTypeFlags.PEP604;
}
@@ -527,6 +527,11 @@ export enum Reachability {
export interface PrintTypeOptions {
expandTypeAlias?: boolean;
enforcePythonSyntax?: boolean;
// Viper 方言:类名保留源码中书写时的完整 module 路径(如 `ast.AST`)。
// 与 useFullyQualifiedNames 不同:后者用类型声明所在的 fullName
// (如 `ast.base.AST`、容器 `_list.list`),这里用源码里实际书写的路径,
// 未写限定路径的裸名(`list`/`str`)保持短名。
sourceQualifiedNames?: boolean;
useFullyQualifiedNames?: boolean;
useTypingUnpack?: boolean;
printUnknownWithAny?: boolean;
@@ -878,7 +883,11 @@ export interface TypeEvaluator {
printType: (type: Type, options?: PrintTypeOptions) => string;
printSrcDestTypes: (srcType: Type, destType: Type) => { sourceType: string; destType: string };
printFunctionParts: (type: FunctionType, extraFlags?: PrintTypeFlags) => [string[], string];
printFunctionParts: (
type: FunctionType,
extraFlags?: PrintTypeFlags,
options?: PrintTypeOptions
) => [string[], string];
getTypeCacheEntryCount: () => number;
disposeEvaluator: () => void;
@@ -102,10 +102,36 @@ export const enum PrintTypeFlags {
// Viper 方言:将 NoneType 显示为 CVoid(C 语义 void)。
ViperDialect = 1 << 14,
// Viper 方言:类名保留源码中书写时的完整 module 路径(如 `ast.AST`、`memhub.MemBuddy`)。
// Viper 源码注解习惯写完整 module 路径,而类型的 fullName 是「声明所在模块」
// (如 `ast.base.AST`、容器类 `_list.list`),两者并不一致,故只能按源码书写形式展示:
// 写了限定路径的保留路径,写裸名的(`list`/`str`)仍显示短名。
ViperSourceQualifiedNames = 1 << 15,
}
export type FunctionReturnTypeCallback = (type: FunctionType) => Type;
// 计算类的显示名(不含 aliasName 与 rawptr 等特殊覆盖)。
// 该函数必须被 printObjectTypeForClassInternal 与 UniqueNameMap 同时使用,
// 否则「名字唯一性判定」与「实际打印名」不一致会导致名字被错误地降级/保留。
function getPrintableClassName(type: ClassType, printTypeFlags: PrintTypeFlags): string {
// Viper 方言(展示模式):按源码书写的完整 module 路径显示,
// 未写限定路径的裸名(`list`/`str` 等)保持短名。
if (
(printTypeFlags & PrintTypeFlags.ViperSourceQualifiedNames) !== 0 &&
(printTypeFlags & PrintTypeFlags.ViperDialect) !== 0
) {
return type.priv.displayName ?? type.shared.name;
}
if ((printTypeFlags & PrintTypeFlags.UseFullyQualifiedNames) !== 0) {
return type.shared.fullName;
}
return type.shared.name;
}
export function printType(
type: Type,
printTypeFlags: PrintTypeFlags,
@@ -1010,8 +1036,7 @@ function printObjectTypeForClassInternal(
): string {
let objName = type.priv.aliasName;
if (!objName) {
objName =
(printTypeFlags & PrintTypeFlags.UseFullyQualifiedNames) !== 0 ? type.shared.fullName : type.shared.name;
objName = getPrintableClassName(type, printTypeFlags);
}
// Special-case NoneType to convert it to None.
@@ -1523,10 +1548,7 @@ class UniqueNameMap {
let className = type.priv.aliasName;
if (!className) {
className =
(this._printTypeFlags & PrintTypeFlags.UseFullyQualifiedNames) !== 0
? type.shared.fullName
: type.shared.name;
className = getPrintableClassName(type, this._printTypeFlags);
}
this._addIfUnique(className, type);
@@ -810,6 +810,11 @@ export interface ClassDetailsPriv {
// name.
aliasName?: string | undefined;
// Viper 扩展:该类型在源码中被引用时书写的完整路径(如 `ast.AST`、`memhub.MemBuddy`)。
// Viper 源码注解习惯写完整 module 路径,类型展示需要保留它而不是退化为短名。
// 该字段**仅用于展示**(typePrinter 读取),不参与任何类型判定/兼容性比较。
displayName?: string | undefined;
// Used for "narrowing" of typed dicts where some entries
// that are not required have been confirmed to be present
// through the use of a guard expression.
@@ -985,6 +990,17 @@ export namespace ClassType {
return newClassType;
}
// Viper 扩展:把「源码中书写类型时使用的完整路径」记到类型上,仅供展示用。
// 已经记录过同一路径时直接返回原类型,避免无意义的克隆。
export function cloneForDisplayName(classType: ClassType, displayName: string): ClassType {
if (classType.priv.displayName === displayName) {
return classType;
}
const newClassType = TypeBase.cloneType(classType);
newClassType.priv.displayName = displayName;
return newClassType;
}
export function cloneForNarrowedTypedDictEntries(
classType: ClassType,
narrowedEntries?: Map<string, TypedDictEntry>
@@ -1146,6 +1146,11 @@ export class CompletionProvider {
}
if (detail?.typeDetail) {
// 围栏语言保持 `python`:宿主渲染时按 languageService.getLanguageIdByLanguageName()
// 解析围栏语言并取该语言的 TM tokenization。本扩展的 `viper` 语法虽能被取到,
// 但只有注释/字符串/控制流关键字/数字等通用 scope 能命中主题规则
// (`def`/`class` 的 storage.type.*.viper 与按 Python 精调的主题规则不匹配,
// 裸类型名语法里没有规则),着色不完整;`python`(MagicPython)齐全,故用它。
markdownString += '```python\n' + detail.typeDetail + '\n```\n';
}
@@ -160,6 +160,11 @@ export function getCompletionItemDocumentation(
declaration: Declaration | undefined
): MarkupContent | undefined {
if (markupKind === MarkupKind.Markdown) {
// 围栏语言保持 `python`:宿主渲染时按 languageService.getLanguageIdByLanguageName()
// 解析围栏语言并取该语言的 TM tokenization。本扩展的 `viper` 语法虽能被取到,
// 但只有注释/字符串/控制流关键字/数字等通用 scope 能命中主题规则
// (`def`/`class` 的 storage.type.*.viper 与按 Python 精调的主题规则不匹配,
// 裸类型名语法里没有规则),着色不完整;`python`(MagicPython)齐全,故用它。
let markdownString = '```python\n' + typeDetail + '\n```\n';
if (documentation) {
@@ -69,6 +69,7 @@ import {
} from './tooltipUtils';
export interface HoverTextPart {
// 该部分按代码块渲染(markdown 下加代码围栏,见 convertHoverResults)。
python?: boolean;
text: string;
}
@@ -87,6 +88,16 @@ export function convertHoverResults(hoverResults: HoverResults | null, format: M
.map((part) => {
if (part.python) {
if (format === MarkupKind.Markdown) {
// 围栏语言保持 `python`:hover 代码块由宿主 markdown 渲染器着色,它先用
// languageService.getLanguageIdByLanguageName() 把围栏语言名解析成语言 id,
// 再取该语言的 TM tokenization,取不到才整块退化为纯文本。
// 本扩展贡献的 `viper` 语法在该路径下能取到 tokenization,但只有通用 scope
// (comment/string/keyword.control.flow/constant.numeric)能命中主题规则;
// `def`/`class` 用的 storage.type.*.viper 匹配不到主题里按 Python 精调的
// 规则,裸类型名(int/str)语法里也没有规则 —— 实测 ```viper 只部分着色,
// 而 ```python 走 MagicPython,关键字/类型/装饰器齐全,故用它。
// Viper 语法本就是 CPython 派生,Python 语法对关键字/装饰器/字符串/数字的
// 着色是正确的。
return '```python\n' + part.text + '\n```\n';
} else if (format === MarkupKind.PlainText) {
return part.text + '\n\n';
@@ -217,7 +228,8 @@ export function getVariableTypeText(
type: Type,
typeNode: ExpressionNode,
functionSignatureDisplay: SignatureDisplayType,
sourceMapper?: SourceMapper
sourceMapper?: SourceMapper,
printOptions?: PrintTypeOptions
) {
let label = 'variable';
if (declaration) {
@@ -235,6 +247,7 @@ export function getVariableTypeText(
} else {
// Handle type aliases specially.
const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), {
...printOptions,
expandTypeAlias: true,
});
@@ -254,6 +267,7 @@ export function getVariableTypeText(
(declaration.typeAnnotationNode as any).d?.member?.d?.value === 'CTypedef'
) {
const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), {
...printOptions,
expandTypeAlias: false,
});
return `(type) ${name} = ` + typeText;
@@ -272,11 +286,13 @@ export function getVariableTypeText(
/* isProperty */ false,
functionSignatureDisplay,
typeNode,
sourceMapper
sourceMapper,
printOptions
);
}
const typeText = typeVarName ?? name + ': ' + evaluator.printType(getTypeForToolTip(evaluator, typeNode));
const typeText =
typeVarName ?? name + ': ' + evaluator.printType(getTypeForToolTip(evaluator, typeNode), printOptions);
// Viper 扩展:变量类型是 typedef 别名时,附加 typedef 信息换行显示。
// 例如 `k: UINT16PTR` 悬浮显示:
@@ -293,10 +309,10 @@ export function getVariableTypeText(
const fileInfo = getFileInfo(typeNode);
if (IsViperDialect(fileInfo.executionEnvironment.dialect)) {
const aliasName = aliasInfo.shared.name;
const actualType = evaluator.printType(
convertToInstance(getTypeForToolTip(evaluator, typeNode)),
{ expandTypeAlias: false }
);
const actualType = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), {
...printOptions,
expandTypeAlias: false,
});
return `(${label}) ` + typeText + `\n(typedef) ${aliasName} = ` + actualType;
}
}
@@ -356,6 +372,16 @@ export class HoverProvider {
return this._program.configOptions.functionSignatureDisplay;
}
// Viper 方言:hover 的类型展示保留源码中书写时的完整 module 路径(如 `ast.AST`)。
// 未写限定路径的裸名(`list`/`str`)仍显示短名,具体规则见 typePrinter。
private _getPrintTypeOptions(options?: PrintTypeOptions): PrintTypeOptions {
return { ...options, sourceQualifiedNames: true };
}
private _printType(type: Type, options?: PrintTypeOptions): string {
return this._evaluator.printType(type, this._getPrintTypeOptions(options));
}
private _getHoverResult(): HoverResults | null {
throwIfCancellationRequested(this._token);
@@ -422,7 +448,10 @@ export class HoverProvider {
node.d.value,
this._evaluator,
isProperty,
this._functionSignatureDisplay
this._functionSignatureDisplay,
/* typeNode */ undefined,
/* sourceMapper */ undefined,
this._getPrintTypeOptions()
);
}
@@ -515,7 +544,8 @@ export class HoverProvider {
type,
typeNode,
this._functionSignatureDisplay,
this._sourceMapper
this._sourceMapper,
this._getPrintTypeOptions()
);
this._addResultsPart(parts, typeText, /* python */ true);
@@ -535,7 +565,7 @@ export class HoverProvider {
if (aliasInfo && aliasInfo.shared.name !== node.d.value) {
const fileInfo = getFileInfo(node);
if (IsViperDialect(fileInfo.executionEnvironment.dialect)) {
const actualType = this._evaluator.printType(
const actualType = this._printType(
convertToInstance(getTypeForToolTip(this._evaluator, node)),
{ expandTypeAlias: false }
);
@@ -605,7 +635,8 @@ export class HoverProvider {
isProperty,
this._functionSignatureDisplay,
/* typeNode */ undefined,
this._sourceMapper
this._sourceMapper,
this._getPrintTypeOptions()
);
this._addResultsPart(parts, signatureString, /* python */ true);
@@ -626,7 +657,7 @@ export class HoverProvider {
case DeclarationType.TypeAlias: {
const type = convertToInstance(this._getType(node));
const typeText = this._evaluator.printType(type, { expandTypeAlias: true });
const typeText = this._printType(type, { expandTypeAlias: true });
this._addResultsPart(parts, `(type) ${node.d.value} = ${typeText}`, /* python */ true);
this._addDocumentationPart(parts, node, resolvedDecl);
break;
@@ -699,7 +730,7 @@ export class HoverProvider {
// 返回类型
const returnType = this._getViperFunctionReturnType(funcNode);
if (returnType) {
lines.push(`${bold('返回类型')}: ${code(this._evaluator.printType(convertToInstance(returnType)))}`);
lines.push(`${bold('返回类型')}: ${code(this._printType(convertToInstance(returnType)))}`);
}
if (lines.length === 0) {
@@ -727,7 +758,7 @@ export class HoverProvider {
paramType = this._evaluator.getTypeOfExpression(param.d.name).type;
}
if (paramType) {
text += ': ' + this._evaluator.printType(convertToInstance(paramType));
text += ': ' + this._printType(convertToInstance(paramType));
}
if (param.d.defaultValue) {
@@ -767,7 +798,8 @@ export class HoverProvider {
type,
node,
this._functionSignatureDisplay,
this._sourceMapper
this._sourceMapper,
this._getPrintTypeOptions()
);
}
@@ -785,7 +817,7 @@ export class HoverProvider {
const entry = subtype.shared.typedDictEntries?.knownItems.get(node.d.value);
if (entry) {
// e.g. (key) name: str
const text = '(key) ' + node.d.value + ': ' + this._evaluator.printType(entry.valueType);
const text = '(key) ' + node.d.value + ': ' + this._printType(entry.valueType);
const declarations = ClassType.getSymbolTable(subtype).get(node.d.value)?.getDeclarations();
let declarationWithDoc: Declaration | undefined;
@@ -833,7 +865,13 @@ export class HoverProvider {
if (result.methodType && isFunctionOrOverloaded(result.methodType)) {
this._addResultsPart(
parts,
getConstructorTooltip(node.d.value, result.methodType, this._evaluator, this._functionSignatureDisplay),
getConstructorTooltip(
node.d.value,
result.methodType,
this._evaluator,
this._functionSignatureDisplay,
this._getPrintTypeOptions()
),
/* python */ true
);
@@ -855,7 +893,7 @@ export class HoverProvider {
private _getTypeText(node: ExpressionNode, options?: PrintTypeOptions): string {
const type = this._getType(node);
return ': ' + this._evaluator.printType(type, options);
return ': ' + this._printType(type, options);
}
private _addDocumentationPart(parts: HoverTextPart[], node: NameNode, resolvedDecl: Declaration | undefined) {
@@ -24,7 +24,7 @@ import {
getPropertyDocStringInherited,
getVariableDocString,
} from '../analyzer/typeDocStringUtils';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { PrintTypeOptions, TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { MemberAccessFlags, lookUpClassMember } from '../analyzer/typeUtils';
import {
ClassType,
@@ -67,7 +67,8 @@ export function getToolTipForType(
isProperty: boolean,
functionSignatureDisplay: SignatureDisplayType,
typeNode?: ExpressionNode,
sourceMapper?: SourceMapper
sourceMapper?: SourceMapper,
printOptions?: PrintTypeOptions
): string {
// Support __call__ method for class instances to show the signature of the method
if (type.category === TypeCategory.Class && isClassInstance(type) && typeNode) {
@@ -91,7 +92,8 @@ export function getToolTipForType(
evaluator,
functionSignatureDisplay,
/* columnThreshold */ 70,
sourceMapper
sourceMapper,
printOptions
)}`;
} else if (isFunction(type)) {
signatureString = `${getFunctionTooltip(
@@ -101,11 +103,12 @@ export function getToolTipForType(
evaluator,
isProperty,
functionSignatureDisplay,
sourceMapper
sourceMapper,
printOptions
)}`;
} else {
signatureString = label.length > 0 ? `(${label}) ` : '';
signatureString += `${name}: ${evaluator.printType(type)}`;
signatureString += `${name}: ${evaluator.printType(type, printOptions)}`;
}
return signatureString;
@@ -117,7 +120,8 @@ export function getOverloadedTooltip(
evaluator: TypeEvaluator,
functionSignatureDisplay: SignatureDisplayType,
columnThreshold = 70,
sourceMapper?: SourceMapper
sourceMapper?: SourceMapper,
printOptions?: PrintTypeOptions
) {
let content = '';
const overloads = OverloadedType.getOverloads(type).map((o) =>
@@ -128,7 +132,8 @@ export function getOverloadedTooltip(
evaluator,
/* isProperty */ false,
functionSignatureDisplay,
sourceMapper
sourceMapper,
printOptions
)
);
@@ -157,12 +162,13 @@ export function getFunctionTooltip(
evaluator: TypeEvaluator,
isProperty = false,
functionSignatureDisplay: SignatureDisplayType,
sourceMapper?: SourceMapper
sourceMapper?: SourceMapper,
printOptions?: PrintTypeOptions
) {
const labelFormatted = label.length === 0 ? '' : `(${label}) `;
const indentStr =
functionSignatureDisplay === SignatureDisplayType.formatted ? '\n' + ' '.repeat(functionParamIndentOffset) : '';
let funcParts = evaluator.printFunctionParts(type);
let funcParts = evaluator.printFunctionParts(type, /* extraFlags */ undefined, printOptions);
if (sourceMapper) {
funcParts = replaceStubEllipsisDefaultValues(type, funcParts, sourceMapper);
}
@@ -285,14 +291,15 @@ export function getConstructorTooltip(
constructorName: string,
type: Type,
evaluator: TypeEvaluator,
functionSignatureDisplay: SignatureDisplayType
functionSignatureDisplay: SignatureDisplayType,
printOptions?: PrintTypeOptions
) {
const classText = `class `;
let signature = '';
if (isOverloaded(type)) {
const overloads = OverloadedType.getOverloads(type).map((overload) =>
getConstructorTooltip(constructorName, overload, evaluator, functionSignatureDisplay)
getConstructorTooltip(constructorName, overload, evaluator, functionSignatureDisplay, printOptions)
);
overloads.forEach((overload, index) => {
signature += overload + ': ...' + '\n\n';
@@ -302,7 +309,7 @@ export function getConstructorTooltip(
functionSignatureDisplay === SignatureDisplayType.formatted
? '\n' + ' '.repeat(functionParamIndentOffset)
: ' ';
const funcParts = evaluator.printFunctionParts(type);
const funcParts = evaluator.printFunctionParts(type, /* extraFlags */ undefined, printOptions);
const paramSignature = formatSignature(funcParts, indentStr, functionSignatureDisplay);
signature += `${classText}${constructorName}${paramSignature}`;
}
@@ -87,7 +87,12 @@ def use():
console.log('=== hover 内容 ===');
console.log(contents);
// 签名部分(原有 python 代码块)
// 签名部分:代码围栏语言为 `python`。hover 代码块由宿主 markdown 渲染器用 TM 语法着色,
// `viper` 围栏虽能取到本扩展贡献的语法,但只有注释/字符串/控制流关键字/数字等通用 scope
// 能命中主题规则(`def`/`class` 的 storage.type.*.viper 与主题里按 Python 精调的规则不匹配,
// 裸类型名语法里没有规则),只部分着色;`python`(MagicPython)着色齐全,故用它。
assert(contents.includes('```python'), `should use python code fence: ${contents}`);
assert(!contents.includes('```viper'), `should not use viper code fence: ${contents}`);
assert(contents.includes('(function)'), `should include signature: ${contents}`);
// 调试信息标题
assert(contents.includes('函数调试信息'), `should include debug heading: ${contents}`);
@@ -100,13 +105,13 @@ def use():
// 泛型参数
assert(contents.includes('泛型参数'), `should include 泛型参数: ${contents}`);
assert(contents.includes('[T]'), `should include [T]: ${contents}`);
// 参数
// 参数(类型按源码书写的完整 module 路径展示:注解写的是 `t.CInt`)
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('a: t.CInt'), `should include param a: ${contents}`);
assert(contents.includes('b: t.CInt = 10'), `should include param b with default: ${contents}`);
// 返回类型
assert(contents.includes('返回类型'), `should include 返回类型: ${contents}`);
assert(contents.includes('CInt'), `should include return type: ${contents}`);
assert(contents.includes('t.CInt'), `should include return type: ${contents}`);
program.dispose();
serviceProvider.dispose();
@@ -29,6 +29,7 @@ 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 { Type } from '../analyzer/types';
import { OperatorType } from '../parser/tokenizerTypes';
(global as any).__rootDirectory = path.resolve();
@@ -337,11 +338,19 @@ class Holder:
const ev = evaluator;
const anns: { line: number; type: string }[] = [];
let directType: Type | undefined;
let chRType: Type | undefined;
class AnnCollector extends ParseTreeWalker {
override visitTypeAnnotation(node: TypeAnnotationNode) {
const line = convertOffsetToPosition(node.d.annotation.start, parseResults.parserOutput.lines).line + 1;
const t = ev.getTypeOfAnnotation(node.d.annotation);
if (line === 5) {
chRType = t;
} else if (line === 6) {
directType = t;
}
anns.push({
line: convertOffsetToPosition(node.d.annotation.start, parseResults.parserOutput.lines).line + 1,
line,
type: t ? program.printType(t) : '(undefined)',
});
return true;
@@ -363,6 +372,22 @@ class Holder:
assert(direct, 'direct annotation not found (L6)');
assert(direct.type.includes('rawptr[AST]'), `direct should be rawptr[AST]: ${direct.type}`);
// LSP 展示形态(hover):sourceQualifiedNames → 按源码书写的完整 module 路径显示。
// 源码写 `rawptr[ast.AST]`,故展示 `rawptr[ast.AST]`;源码写裸名 `list`,故仍是 `list`。
assert(directType, 'direct type not captured (L6)');
const directDisplay = program.printType(directType!, { sourceQualifiedNames: true });
console.log(`=== LSP 展示形态 L6 direct: ${directDisplay}`);
assert.strictEqual(directDisplay, 'rawptr[ast.AST]', `direct 应保留源码 module 路径: ${directDisplay}`);
assert(chRType, 'ch_r type not captured (L5)');
const chRDisplay = program.printType(chRType!, { sourceQualifiedNames: true });
console.log(`=== LSP 展示形态 L5 ch_r: ${chRDisplay}`);
assert.strictEqual(
chRDisplay,
'rawptr[list[rawptr[ast.AST]]]',
`ch_r 应保留源码 module 路径且裸名 list 保持短名: ${chRDisplay}`
);
// 不得出现「"[" 未关闭」「rawptr 未定义」等错误(category=0 = Error)
const diagnostics = getDiagnostics(program, configOptions, uri);
const errors = diagnostics.filter((d) => d.includes('category=0'));