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:
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'));
|
||||
|
||||
Reference in new issue
Block a user