Files
Viper_VSCode/packages/vipeye-internal/src/languageService/hoverProvider.ts
T
TermiNexus 525a654f5c feat(vipeye-internal): 支持 enum X: 枚举语法与 define 常量声明
- enum:`enum NAME:` 识别为 Viper 枚举(等价 class NAME(t.CEnum)),
  成员支持裸名与逗号分隔(enum X: a, b, c),并支持同行声明后换行 TAB 续写
- enum:取值按编译期规则推导(从 0 递增,中间显式赋值后从该值 +1 续推),
  hover 显示 (variable) IsAsync: ASTFlag = 0,成员可隐式转换为 int
- enum:成员推导值超出声明类型位宽时报错(如 i1 存不下 2)
- define:`define NAME: 类型 = 值` / `define NAME = 值` 解析为常量声明,
  `define f(...)` 宏函数行为不变
- 新增整数字面量位宽校验:字面量无法存入声明类型时提示扩容,
  define 与普通声明均生效(位宽表源自 wiki 02-type-system.md)
- 新增 viperEnumFlag.test.ts(5 项)与 viperDefineConst.test.ts(4 项)
2026-09-21 23:13:38 +08:00

964 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* hoverProvider.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
* Author: Eric Traut
*
* Logic that maps a position within a Viper program file into
* markdown text that is displayed when the user hovers over that
* position within a smart editor.
*/
import { CancellationToken, Hover, MarkupKind } from 'vscode-languageserver';
import {
Declaration,
DeclarationType,
FunctionDeclaration,
VariableDeclaration,
isUnresolvedAliasDeclaration,
} from '../analyzer/declaration';
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
import { getViperEnumMemberValues } from '../analyzer/enums';
import * as ParseTreeUtils from '../analyzer/parseTreeUtils';
import { SourceMapper } from '../analyzer/sourceMapper';
import { SynthesizedTypeInfo } from '../analyzer/symbol';
import { isBuiltInModule } from '../analyzer/typeDocStringUtils';
import { PrintTypeOptions, TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { convertToInstance, doForEachSubtype, isMaybeDescriptorInstance } from '../analyzer/typeUtils';
import {
ClassType,
Type,
TypeCategory,
getTypeAliasInfo,
isAnyOrUnknown,
isClassInstance,
isFunction,
isFunctionOrOverloaded,
isModule,
isParamSpec,
isTypeVar,
} from '../analyzer/types';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { SignatureDisplayType } from '../common/configOptions';
import { IsViperDialect } from '../common/dialect';
import { assertNever, fail } from '../common/debug';
import { ProgramView } from '../common/extensibility';
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
import { ServiceProvider } from '../common/serviceProvider';
import { Position, Range, TextRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import {
ExpressionNode,
FunctionNode,
NameNode,
ParamCategory,
ParameterNode,
ParseNode,
ParseNodeType,
StringNode,
} from '../parser/parseNodes';
import { ParseFileResults } from '../parser/parser';
import { TokenType } from '../parser/tokenizerTypes';
import {
getClassAndConstructorTypes,
getConstructorTooltip,
getDocumentationPartsForTypeAndDeclWithSource,
getToolTipForType,
getTypeForToolTip,
getWrappedFunctionType,
} from './tooltipUtils';
export interface HoverTextPart {
// 该部分按代码块渲染(markdown 下加代码围栏,见 convertHoverResults)。
python?: boolean;
text: string;
}
export interface HoverResults {
parts: HoverTextPart[];
range: Range;
}
export function convertHoverResults(hoverResults: HoverResults | null, format: MarkupKind): Hover | null {
if (!hoverResults) {
return null;
}
const markupString = hoverResults.parts
.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';
} else {
fail(`Unsupported markup type: ${format}`);
}
}
return part.text;
})
.join('')
.trimEnd();
return {
contents: {
kind: format,
value: markupString,
},
range: hoverResults.range,
};
}
function getDocumentationSeparator(previousPart: HoverTextPart): string {
if (previousPart.python) {
return '---\n';
}
if (previousPart.text.endsWith('\n\n')) {
return '---\n';
}
if (previousPart.text.endsWith('\n')) {
return '\n---\n';
}
return '\n\n---\n';
}
export function addParameterResultsPart(
serviceProvider: ServiceProvider,
paramNameNode: NameNode,
resolvedDecl: Declaration | undefined,
format: MarkupKind,
parts: HoverTextPart[]
) {
// See if we have a docstring for the parent function.
let docString: string | undefined = undefined;
const funcNode = ParseTreeUtils.getEnclosingFunction(resolvedDecl?.node || paramNameNode);
if (funcNode) {
docString = ParseTreeUtils.getDocString(funcNode?.d.suite?.d.statements ?? []);
if (docString) {
// Compute the docstring now.
docString = serviceProvider
.docStringService()
.extractParameterDocumentation(docString, paramNameNode.d.value, format);
}
}
if (!docString) {
return;
}
parts.push({
python: false,
text: docString,
});
}
function addReturnResultsPart(
serviceProvider: ServiceProvider,
functionNode: FunctionNode,
format: MarkupKind,
parts: HoverTextPart[]
) {
// See if we have a docstring for the function whose return arrow is being hovered.
const docString = ParseTreeUtils.getDocString(functionNode.d.suite?.d.statements ?? []);
if (!docString) {
return;
}
const returnDoc = serviceProvider.docStringService().extractReturnDocumentation(docString, format);
if (!returnDoc) {
return;
}
parts.push({
python: false,
text: returnDoc,
});
}
export function addDocumentationResultsPart(
serviceProvider: ServiceProvider,
docString: string | undefined,
format: MarkupKind,
parts: HoverTextPart[],
resolvedDecl: Declaration | undefined,
forceLiteralOverride?: boolean
) {
if (!docString) {
return;
}
if (format === MarkupKind.Markdown) {
const forceLiteral = forceLiteralOverride ?? isBuiltInModule(resolvedDecl?.uri);
const markDown = serviceProvider
.docStringService()
.convertDocStringToMarkdown(docString, forceLiteral, resolvedDecl?.uri);
if (parts.length > 0 && markDown.length > 0) {
parts.push({ text: getDocumentationSeparator(parts[parts.length - 1]) });
}
parts.push({ text: markDown, python: false });
return;
}
if (format === MarkupKind.PlainText) {
parts.push({ text: serviceProvider.docStringService().convertDocStringToPlainText(docString), python: false });
return;
}
fail(`Unsupported markup type: ${format}`);
}
// Viper 扩展:构造 `enum X:` 枚举成员的 hover 类型文本(枚举类型 + 编译期推导值),
// 例如 `ASTFlag = 0`。非 `enum X:` 的成员返回 undefined,走常规显示逻辑。
function getViperEnumMemberTypeText(declaration: VariableDeclaration | undefined, name: string): string | undefined {
if (!declaration || declaration.node.nodeType !== ParseNodeType.Name) {
return undefined;
}
const classNode = ParseTreeUtils.getEnclosingClass(declaration.node, /* stopAtFunction */ true);
if (!classNode?.d.isViperEnum) {
return undefined;
}
const className = classNode.d.name.d.value;
const value = getViperEnumMemberValues(classNode).get(name);
return value === undefined ? `${name}: ${className}` : `${name}: ${className} = ${value}`;
}
export function getVariableTypeText(
evaluator: TypeEvaluator,
declaration: VariableDeclaration | undefined,
name: string,
type: Type,
typeNode: ExpressionNode,
functionSignatureDisplay: SignatureDisplayType,
sourceMapper?: SourceMapper,
printOptions?: PrintTypeOptions
) {
let label = 'variable';
if (declaration) {
label = declaration.isConstant || evaluator.isFinalVariableDeclaration(declaration) ? 'constant' : 'variable';
}
// Viper 扩展:`enum X:` 枚举成员显示枚举类型与编译期推导值(如 `ASTFlag = 0`)。
// 裸名成员没有赋值也没有注解,常规路径只会给出 Unbound。
const viperEnumMemberText = getViperEnumMemberTypeText(declaration, name);
if (viperEnumMemberText) {
return `(${label}) ` + viperEnumMemberText;
}
let typeVarName: string | undefined;
if (type.props?.typeAliasInfo && typeNode.nodeType === ParseNodeType.Name) {
const typeAliasInfo = getTypeAliasInfo(type);
if (typeAliasInfo?.shared.name === typeNode.d.value) {
if (isTypeVar(type)) {
label = isParamSpec(type) ? 'param spec' : 'type variable';
typeVarName = type.shared.name;
} else {
// Handle type aliases specially.
const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), {
...printOptions,
expandTypeAlias: true,
});
return `(type) ${name} = ` + typeText;
}
}
}
// Viper 扩展:t.CTypedef 声明的类型别名悬浮提示。
// 当变量声明带 t.CTypedef 注解时(如 `UINT32PTR: t.CTypedef = t.CUInt32T | t.CPtr`),
// 即使 typeAliasInfo 未被设置(跨模块 import 等场景),也显示类型别名格式。
// 格式:(type) name = actualType。不展开类型别名(expandTypeAlias: false),
// 保留原始 typedef 表达式中的类型名称(如 CUInt32T 而非 int)。
if (
declaration?.typeAnnotationNode &&
declaration.typeAnnotationNode.nodeType === ParseNodeType.MemberAccess &&
(declaration.typeAnnotationNode as any).d?.member?.d?.value === 'CTypedef'
) {
const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), {
...printOptions,
expandTypeAlias: false,
});
return `(type) ${name} = ` + typeText;
}
if (
type.category === TypeCategory.Function ||
type.category === TypeCategory.Overloaded ||
typeNode.parent?.nodeType === ParseNodeType.Call
) {
return getToolTipForType(
type,
label,
name,
evaluator,
/* isProperty */ false,
functionSignatureDisplay,
typeNode,
sourceMapper,
printOptions
);
}
const typeText =
typeVarName ?? name + ': ' + evaluator.printType(getTypeForToolTip(evaluator, typeNode), printOptions);
// Viper 扩展:变量类型是 typedef 别名时,附加 typedef 信息换行显示。
// 例如 `k: UINT16PTR` 悬浮显示:
// (variable) k: UINT16PTR
// (typedef) UINT16PTR = CPtr[CUInt16T]
// 仅当变量类型带有 typeAliasInfo 且别名名与变量名不同时触发
// (避免类型别名定义本身悬浮时重复显示)。
if (
typeNode.nodeType === ParseNodeType.Name &&
type.props?.typeAliasInfo
) {
const aliasInfo = getTypeAliasInfo(type);
if (aliasInfo && aliasInfo.shared.name !== typeNode.d.value) {
const fileInfo = getFileInfo(typeNode);
if (IsViperDialect(fileInfo.executionEnvironment.dialect)) {
const aliasName = aliasInfo.shared.name;
const actualType = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), {
...printOptions,
expandTypeAlias: false,
});
return `(${label}) ` + typeText + `\n(typedef) ${aliasName} = ` + actualType;
}
}
}
return `(${label}) ` + typeText;
}
export class HoverProvider {
private readonly _parseResults: ParseFileResults | undefined;
private readonly _sourceMapper: SourceMapper;
constructor(
private readonly _program: ProgramView,
private readonly _fileUri: Uri,
private readonly _position: Position,
private readonly _format: MarkupKind,
private readonly _token: CancellationToken
) {
this._parseResults = this._program.getParseResults(this._fileUri);
this._sourceMapper = this._program.getSourceMapper(this._fileUri, this._token, /* mapCompiled */ true);
}
getHover(): Hover | null {
return convertHoverResults(this._getHoverResult(), this._format);
}
static getPrimaryDeclaration(declarations: Declaration[]) {
// In most cases, it's best to treat the first declaration as the
// "primary". This works well for properties that have setters
// which often have doc strings on the getter but not the setter.
// The one case where using the first declaration doesn't work as
// well is the case where an import statement within an __init__.py
// file uses the form "from .A import A". In this case, if we use
// the first declaration, it will show up as a module rather than
// the imported symbol type.
const primaryDeclaration = declarations[0];
if (primaryDeclaration.type === DeclarationType.Alias && declarations.length > 1) {
return declarations[1];
} else if (
primaryDeclaration.type === DeclarationType.Variable &&
declarations.length > 1 &&
primaryDeclaration.isDefinedBySlots
) {
// Slots cannot have docstrings, so pick the secondary.
return declarations[1];
}
return primaryDeclaration;
}
private get _evaluator(): TypeEvaluator {
return this._program.evaluator!;
}
private get _functionSignatureDisplay() {
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);
if (!this._parseResults) {
return null;
}
const offset = convertPositionToOffset(this._position, this._parseResults.tokenizerOutput.lines);
if (offset === undefined) {
return null;
}
const node = ParseTreeUtils.findNodeByOffset(this._parseResults.parserOutput.parseTree, offset);
if (node === undefined) {
return null;
}
const results: HoverResults = {
parts: [],
range: {
start: convertOffsetToPosition(node.start, this._parseResults.tokenizerOutput.lines),
end: convertOffsetToPosition(TextRange.getEnd(node), this._parseResults.tokenizerOutput.lines),
},
};
if (node.nodeType === ParseNodeType.Name) {
const declInfo = this._evaluator.getDeclInfoForNameNode(node);
const declarations = declInfo?.decls;
if (declarations && declarations.length > 0) {
const primaryDeclaration = HoverProvider.getPrimaryDeclaration(declarations);
this._addResultsForDeclaration(results.parts, primaryDeclaration, node);
} else if (declInfo && declInfo.synthesizedTypes.length > 0) {
const nameNode = node;
declInfo?.synthesizedTypes.forEach((type) => {
this._addResultsForSynthesizedType(results.parts, type, nameNode);
});
this._addDocumentationPart(results.parts, node, /* resolvedDecl */ undefined);
} else if (!node.parent || node.parent.nodeType !== ParseNodeType.ModuleName) {
// If we had no declaration, see if we can provide a minimal tooltip. We'll skip
// this if it's part of a module name, since a module name part with no declaration
// is a directory (a namespace package), and we don't want to provide any hover
// information in that case.
if (results.parts.length === 0) {
const type = this._getType(node);
let typeText: string;
if (isModule(type)) {
// Handle modules specially because submodules aren't associated with
// declarations, but we want them to be presented in the same way as
// the top-level module, which does have a declaration.
typeText = '(module) ' + node.d.value;
} else {
let label = 'function';
let isProperty = false;
if (isMaybeDescriptorInstance(type, /* requireSetter */ false)) {
isProperty = true;
label = 'property';
}
typeText = getToolTipForType(
type,
label,
node.d.value,
this._evaluator,
isProperty,
this._functionSignatureDisplay,
/* typeNode */ undefined,
/* sourceMapper */ undefined,
this._getPrintTypeOptions()
);
}
this._addResultsPart(results.parts, typeText, /* python */ true);
this._addDocumentationPart(results.parts, node, /* resolvedDecl */ undefined);
}
}
} else if (node.nodeType === ParseNodeType.String) {
const type = this._evaluator.getExpectedType(node)?.type;
if (type !== undefined) {
this._tryAddPartsForTypedDictKey(node, type, results.parts);
}
} else if (node.nodeType === ParseNodeType.Function) {
// Hovering over the `->` return-type arrow shows the function's return documentation.
const token = ParseTreeUtils.getTokenOverlapping(this._parseResults.tokenizerOutput.tokens, offset);
if (token?.type === TokenType.Arrow) {
addReturnResultsPart(this._program.serviceProvider, node, this._format, results.parts);
}
}
return results.parts.length > 0 ? results : null;
}
private _addResultsForDeclaration(parts: HoverTextPart[], declaration: Declaration, node: NameNode): void {
const resolvedDecl =
declaration.type === DeclarationType.Alias
? this._evaluator.resolveAliasDeclaration(declaration, /* resolveLocalNames */ true)
: declaration;
if (
!resolvedDecl ||
(resolvedDecl.type === DeclarationType.Alias && isUnresolvedAliasDeclaration(resolvedDecl))
) {
this._addResultsPart(parts, `(import) ` + node.d.value + this._getTypeText(node), /* python */ true);
return;
}
switch (resolvedDecl.type) {
case DeclarationType.Intrinsic: {
this._addResultsPart(parts, node.d.value + this._getTypeText(node), /* python */ true);
this._addDocumentationPart(parts, node, resolvedDecl);
break;
}
case DeclarationType.Variable: {
// If the named node is an aliased import symbol, we can't call
// getType on the original name because it's not in the symbol
// table. Instead, use the node from the resolved alias.
let typeNode: ParseNode = node;
let cachedTypeForTypeNode: Type | undefined;
if (
declaration.node.nodeType === ParseNodeType.ImportAs ||
declaration.node.nodeType === ParseNodeType.ImportFromAs
) {
if (declaration.node.d.alias && node !== declaration.node.d.alias) {
if (resolvedDecl.node.nodeType === ParseNodeType.Name) {
typeNode = resolvedDecl.node;
}
}
} else if (node.parent?.nodeType === ParseNodeType.Argument && node.parent.d.name === node) {
// If this is a named argument, we would normally have received a Parameter declaration
// rather than a variable declaration, but we can get here in the case of a dataclass.
if (declaration.node.nodeType === ParseNodeType.Name) {
if (!resolvedDecl.typeAnnotationNode) {
// No annotation: use the declaration name node.
typeNode = declaration.node;
} else {
// Has annotation: prefer the call-site node so the type evaluator
// returns the declared annotation type (not flow-narrowed by the default).
// Fall back to declaration node if the call-site resolves to Any/Unknown
// (e.g. NamedTuple synthesized constructors).
const callSiteType = this._getType(node);
if (isAnyOrUnknown(callSiteType)) {
typeNode = declaration.node;
} else {
// typeNode stays as `node`; reuse the type we just computed to
// avoid a duplicate _getType call on this hot hover path.
cachedTypeForTypeNode = callSiteType;
}
}
}
}
// Determine if this identifier is a type alias. If so, expand
// the type alias when printing the type information.
const type = cachedTypeForTypeNode ?? this._getType(typeNode);
const typeText = getVariableTypeText(
this._evaluator,
resolvedDecl,
node.d.value,
type,
typeNode,
this._functionSignatureDisplay,
this._sourceMapper,
this._getPrintTypeOptions()
);
this._addResultsPart(parts, typeText, /* python */ true);
this._addDocumentationPart(parts, node, resolvedDecl);
break;
}
case DeclarationType.Param: {
// Viper 扩展:参数类型是 typedef 别名时,附加 typedef 信息换行显示。
// 例如 `fb: UINT32PTR` 悬浮显示:
// (parameter) fb: UINT32PTR
// (typedef) UINT32PTR = CPtr[CUInt32T]
const paramType = this._getType(node);
let paramLine = '(parameter) ' + node.d.value + this._getTypeText(node);
if (paramType?.props?.typeAliasInfo && node.nodeType === ParseNodeType.Name) {
const aliasInfo = getTypeAliasInfo(paramType);
if (aliasInfo && aliasInfo.shared.name !== node.d.value) {
const fileInfo = getFileInfo(node);
if (IsViperDialect(fileInfo.executionEnvironment.dialect)) {
const actualType = this._printType(
convertToInstance(getTypeForToolTip(this._evaluator, node)),
{ expandTypeAlias: false }
);
paramLine += `\n(typedef) ${aliasInfo.shared.name} = ` + actualType;
}
}
}
this._addResultsPart(parts, paramLine, /* python */ true);
addParameterResultsPart(this._program.serviceProvider, node, resolvedDecl, this._format, parts);
this._addDocumentationPart(parts, node, resolvedDecl);
break;
}
case DeclarationType.TypeParam: {
// If the user is hovering over a type parameter name in a class type parameter
// list, display the computed variance of the type param.
const typeParamListNode = ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.TypeParameterList);
const nodeType = typeParamListNode?.parent?.nodeType;
const printTypeVarVariance = nodeType === ParseNodeType.Class || nodeType === ParseNodeType.TypeAlias;
this._addResultsPart(
parts,
'(type parameter) ' + node.d.value + this._getTypeText(node, { printTypeVarVariance }),
/* python */ true
);
this._addDocumentationPart(parts, node, resolvedDecl);
break;
}
case DeclarationType.Class:
case DeclarationType.SpecialBuiltInClass: {
if (this._addInitOrNewMethodInsteadIfCallNode(node, parts, resolvedDecl)) {
return;
}
const nameNode = resolvedDecl.type === DeclarationType.Class ? resolvedDecl.node.d.name : node;
this._addResultsPart(parts, '(class) ' + nameNode.d.value, /* python */ true);
this._addDocumentationPart(parts, node, resolvedDecl);
break;
}
case DeclarationType.Function: {
let label = 'function';
let isProperty = false;
if (resolvedDecl.isMethod) {
const declaredType = this._evaluator.getTypeForDeclaration(resolvedDecl)?.type;
isProperty = !!declaredType && isMaybeDescriptorInstance(declaredType, /* requireSetter */ false);
label = isProperty ? 'property' : 'method';
}
let type = this._getType(node);
const resolvedType = this._getType(resolvedDecl.node.d.name);
type = isAnyOrUnknown(type) ? resolvedType : type;
// If this function is decorated with @functools.wraps, show the wrapped function's signature.
if (isFunction(type)) {
const wrappedType = getWrappedFunctionType(resolvedDecl as FunctionDeclaration, this._evaluator);
if (wrappedType) {
type = wrappedType;
}
}
const signatureString = getToolTipForType(
type,
label,
node.d.value,
this._evaluator,
isProperty,
this._functionSignatureDisplay,
/* typeNode */ undefined,
this._sourceMapper,
this._getPrintTypeOptions()
);
this._addResultsPart(parts, signatureString, /* python */ true);
// Viper 扩展:hover 函数时附加详尽调试信息(仅 Viper 方言)。
this._addViperFunctionDebugPart(parts, resolvedDecl);
this._addDocumentationPart(parts, node, resolvedDecl);
break;
}
case DeclarationType.Alias: {
// First the 'module' header.
this._addResultsPart(parts, '(module) ' + node.d.value, /* python */ true);
this._addDocumentationPart(parts, node, resolvedDecl);
break;
}
case DeclarationType.TypeAlias: {
const type = convertToInstance(this._getType(node));
const typeText = this._printType(type, { expandTypeAlias: true });
this._addResultsPart(parts, `(type) ${node.d.value} = ${typeText}`, /* python */ true);
this._addDocumentationPart(parts, node, resolvedDecl);
break;
}
default:
assertNever(resolvedDecl);
}
}
// 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._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._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;
if (isModule(typeInfo.type)) {
typeText = '(module) ' + hoverNode.d.value;
} else {
const node = typeInfo.node ?? hoverNode;
const type = this._getType(node);
typeText = getVariableTypeText(
this._evaluator,
/* declaration */ undefined,
node.d.value,
type,
node,
this._functionSignatureDisplay,
this._sourceMapper,
this._getPrintTypeOptions()
);
}
if (typeText) {
this._addResultsPart(parts, typeText, /* python */ true);
}
}
private _tryAddPartsForTypedDictKey(node: StringNode, type: Type, parts: HoverTextPart[]) {
// If the expected type is a TypedDict and the current node is a key entry then we can provide a tooltip
// with the type of the TypedDict key and its docstring, if available.
const seenEntries = new Set<string>();
doForEachSubtype(type, (subtype) => {
if (isClassInstance(subtype) && ClassType.isTypedDictClass(subtype)) {
const entry = subtype.shared.typedDictEntries?.knownItems.get(node.d.value);
if (entry) {
// e.g. (key) name: str
const text = '(key) ' + node.d.value + ': ' + this._printType(entry.valueType);
const declarations = ClassType.getSymbolTable(subtype).get(node.d.value)?.getDeclarations();
let declarationWithDoc: Declaration | undefined;
let docString = '';
if (declarations !== undefined && declarations?.length !== 0) {
// As we are just interested in the docString we don't have to worry about
// anything other than the first declaration. There also shouldn't be more
// than one declaration for a TypedDict key variable.
const declaration = declarations[0];
if (declaration.type === DeclarationType.Variable && declaration.docString !== undefined) {
declarationWithDoc = declaration;
docString = declaration.docString;
}
}
const dedupKey = `${text}\n${docString}`;
if (seenEntries.has(dedupKey)) {
return;
}
seenEntries.add(dedupKey);
// If we have already added parts for another declaration (e.g. for a union of TypedDicts that share the same key)
// then we need to add a separator to prevent a visual bug.
if (parts.length > 0) {
parts.push({ text: '\n\n---\n' });
}
this._addResultsPart(parts, text, /* python */ true);
if (declarationWithDoc) {
this._addDocumentationPartForType(parts, subtype, declarationWithDoc);
}
}
}
});
}
private _addInitOrNewMethodInsteadIfCallNode(node: NameNode, parts: HoverTextPart[], declaration: Declaration) {
const result = getClassAndConstructorTypes(node, this._evaluator);
if (!result) {
return false;
}
if (result.methodType && isFunctionOrOverloaded(result.methodType)) {
this._addResultsPart(
parts,
getConstructorTooltip(
node.d.value,
result.methodType,
this._evaluator,
this._functionSignatureDisplay,
this._getPrintTypeOptions()
),
/* python */ true
);
const addedDoc = this._addDocumentationPartForType(parts, result.methodType, declaration);
if (!addedDoc) {
this._addDocumentationPartForType(parts, result.classType, declaration);
}
return true;
}
return false;
}
private _getType(node: ExpressionNode) {
// It does common work necessary for hover for a type we got
// from raw type evaluator.
return getTypeForToolTip(this._evaluator, node);
}
private _getTypeText(node: ExpressionNode, options?: PrintTypeOptions): string {
const type = this._getType(node);
return ': ' + this._printType(type, options);
}
private _addDocumentationPart(parts: HoverTextPart[], node: NameNode, resolvedDecl: Declaration | undefined) {
const type = this._getType(node);
this._addDocumentationPartForType(parts, type, resolvedDecl, node.d.value);
}
private _addDocumentationPartForType(
parts: HoverTextPart[],
type: Type | undefined,
resolvedDecl: Declaration | undefined,
name?: string
): boolean {
const documentation = getDocumentationPartsForTypeAndDeclWithSource(
this._sourceMapper,
type,
resolvedDecl,
this._evaluator,
{
name,
}
);
addDocumentationResultsPart(
this._program.serviceProvider,
documentation?.text,
this._format,
parts,
resolvedDecl,
documentation?.forceLiteral
);
return !!documentation?.text;
}
private _addResultsPart(parts: HoverTextPart[], text: string, python = false) {
parts.push({
python,
text,
});
}
}