Files
Viper_VSCode/packages/vipeye-internal/src/parser/parser.ts
T
TermiNexus 4d5a0d203b feat(vipeye-internal): __attribute__ 免 import t/c 与状态栏分析进度
- __attribute__ 脱糖出的 t / c 模块根改为「隐式模块根」(零长度合成名 +
  isViperImplicitModule 标记),作用域链取不到时回退 builtins 直接查
  symbolTable,故不再要求源码 import t / import c;手写裸 t / c 仍报未定义。
  补上 builtins.vpi 缺失的 import c(原先只有 import t,装饰器路径一直报
  「未定义 c」)。
- 新增左下角状态栏分析进度:逐文件回调 → 后台线程节流 50ms 回传
  analysisProgress 消息 → 前台 sendNotification('viper/analysisStatus')
  → 客户端 StatusBarItem 显示 `C:\xxx\1.vp (4/90) 4.44% loading...`,
  分析结束自动隐藏,让用户能区分「插件在忙」与「插件卡死」。
- viperAttribute.test.ts 补回被误删的 hover 用例并新增 2 个回归用例;
  jest 794 → 796 通过。
2026-09-27 13:03:03 +08:00

6639 lines
267 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.
/*
* parser.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
* Author: Eric Traut
*
* Based on code from python-language-server repository:
* https://github.com/Microsoft/python-language-server
*
* Parser for the Python language. Converts a stream of tokens
* into an abstract syntax tree (AST).
*/
import { Char } from '../common/charCodes';
import { appendArray } from '../common/collectionUtils';
import { assert } from '../common/debug';
import { Diagnostic, DiagnosticAddendum } from '../common/diagnostic';
import { DiagnosticSink } from '../common/diagnosticSink';
import { LanguageDialect } from '../common/dialect';
import { convertOffsetsToRange } from '../common/positionUtils';
import {
PythonVersion,
latestStablePythonVersion,
pythonVersion3_10,
pythonVersion3_11,
pythonVersion3_12,
pythonVersion3_13,
pythonVersion3_14,
pythonVersion3_15,
pythonVersion3_3,
pythonVersion3_5,
pythonVersion3_6,
pythonVersion3_8,
pythonVersion3_9,
} from '../common/pythonVersion';
import { hashString } from '../common/stringUtils';
import { TextRange } from '../common/textRange';
import { TextRangeCollection } from '../common/textRangeCollection';
import { timingStats } from '../common/timing';
import { LocAddendum, LocMessage } from '../localization/localize';
import {
ArgCategory,
ArgumentNode,
AssertNode,
AssignmentExpressionNode,
AssignmentNode,
AugmentedAssignmentNode,
AwaitNode,
BinaryOperationNode,
BreakNode,
CallNode,
CaseNode,
ClassNode,
ComprehensionForIfNode,
ComprehensionForNode,
ComprehensionIfNode,
ComprehensionNode,
ConstantNode,
ContinueNode,
DecoratorNode,
DelNode,
DictionaryEntryNode,
DictionaryExpandEntryNode,
DictionaryKeyEntryNode,
DictionaryNode,
EllipsisNode,
ErrorExpressionCategory,
ErrorNode,
ExceptNode,
ExpressionNode,
ForNode,
FormatStringNode,
FunctionAnnotationNode,
FunctionNode,
GlobalNode,
IfNode,
ImportAsNode,
ImportFromAsNode,
ImportFromNode,
ImportNode,
IndexNode,
LambdaNode,
ListNode,
MatchNode,
MemberAccessNode,
ModuleNameNode,
ModuleNode,
NameNode,
NonlocalNode,
NumberNode,
ParamCategory,
ParameterNode,
ParseNode,
ParseNodeType,
PassNode,
PatternAsNode,
PatternAtomNode,
PatternCaptureNode,
PatternClassArgumentNode,
PatternClassNode,
PatternLiteralNode,
PatternMappingEntryNode,
PatternMappingExpandEntryNode,
PatternMappingKeyEntryNode,
PatternMappingNode,
PatternSequenceNode,
PatternValueNode,
RaiseNode,
ReturnNode,
SetNode,
SliceNode,
StatementListNode,
StatementNode,
StringListNode,
StringNode,
SuiteNode,
TernaryNode,
TryNode,
TupleNode,
TypeAliasNode,
TypeAnnotationNode,
TypeParamKind,
TypeParameterListNode,
TypeParameterNode,
UnaryOperationNode,
UnpackNode,
WhileNode,
WithItemNode,
WithNode,
YieldFromNode,
YieldNode,
extendRange,
getNextNodeId,
} from './parseNodes';
import * as StringTokenUtils from './stringTokenUtils';
import { Tokenizer, TokenizerOutput } from './tokenizer';
import {
DedentToken,
FStringEndToken,
FStringMiddleToken,
FStringStartToken,
IdentifierToken,
IndentToken,
KeywordToken,
KeywordType,
NumberToken,
OperatorToken,
OperatorType,
StringToken,
StringTokenFlags,
Token,
TokenType,
} from './tokenizerTypes';
interface ListResult<T> {
list: T[];
trailingComma: boolean;
parseError?: ErrorNode | undefined;
}
interface SubscriptListResult {
list: ArgumentNode[];
trailingComma: boolean;
}
const commentRegEx = /^(\s*#\s*type:\s*)([^\r\n]*)/;
const ignoreCommentRegEx = /^ignore(\s|\[|$)/;
export class ParseOptions {
isStubFile: boolean;
pythonVersion: PythonVersion;
reportInvalidStringEscapeSequence: boolean;
skipFunctionAndClassBody: boolean;
useNotebookMode: boolean;
reportErrorsForParsedStringContents: boolean;
// Viper 方言开关;undefined 表示使用默认 Python 语义。
dialect: LanguageDialect | undefined;
constructor() {
this.isStubFile = false;
this.pythonVersion = latestStablePythonVersion;
this.reportInvalidStringEscapeSequence = false;
this.skipFunctionAndClassBody = false;
this.useNotebookMode = false;
this.reportErrorsForParsedStringContents = false;
this.dialect = undefined;
}
}
export interface ParserOutput {
parseTree: ModuleNode;
importedModules: ModuleImport[];
futureImports: Set<string>;
containsWildcardImport: boolean;
typingSymbolAliases: Map<string, string>;
hasTypeAnnotations: boolean;
lines: TextRangeCollection<TextRange>;
}
export interface ParseFileResults {
text: string;
contentHash: number;
parserOutput: ParserOutput;
tokenizerOutput: TokenizerOutput;
}
export interface ParseExpressionTextResults<T extends ParseNode> {
parseTree?: T | undefined;
lines: TextRangeCollection<TextRange>;
diagnostics: Diagnostic[];
}
export interface ModuleImport {
nameNode: ModuleNameNode;
leadingDots: number;
nameParts: string[];
// Used for "from X import Y" pattern. An empty
// array implies "from X import *".
importedSymbols: Set<string> | undefined;
}
export interface ArgListResult {
args: ArgumentNode[];
trailingComma: boolean;
}
export const enum ParseTextMode {
Expression,
VariableAnnotation,
FunctionAnnotation,
}
// Limit the max child node depth to prevent stack overflows.
const maxChildNodeDepth = 256;
export class Parser {
private _fileContents?: string;
private _tokenizerOutput?: TokenizerOutput;
private _tokens?: TextRangeCollection<Token>;
private _tokenCount = 0;
private _tokenIndex = 0;
private _areErrorsSuppressed = false;
private _parseOptions: ParseOptions = new ParseOptions();
private _diagSink: DiagnosticSink = new DiagnosticSink();
private _isInLoop = false;
private _isInFunction = false;
private _isInExceptionGroup = false;
private _isParsingTypeAnnotation = false;
private _isParsingIndexTrailer = false;
// Viper 扩展:正在解析 `[SIZE]T` 的元素类型(此时即使身处下标实参内,
// 也允许再出现 `[SIZE]T`,使 `list[[2][3]int]` 这类嵌套合法)。
private _isParsingViperBracketArrayElement = false;
// Viper 扩展:正在解析类型描述符(const / volatile)修饰的主体类型。
// 此时禁止在表达式层就地消费 C 指针后缀 `*`,使其统一冒泡到最外层
// _parseTypeAnnotation 应用 —— 保证 `const T*` 恒为 `(CConst | T)*`,
// 不因 `*` 之后是行尾还是下一行内容而在两条解析路径间摇摆
// (见 _isAtViperPointerTypeSuffix 对换行的处理)。
private _isParsingViperTypeDescriptorInner = false;
// Viper 扩展:当前所处的类声明形态,用于校验 vtable 修饰方法的出现位置:
// `virtual def` 仅允许位于 class / virtual class 内;
// `novirtual def` 允许位于 class / virtual class / novirtual class 内。
// 进入函数体(suite)时置为 undefined:方法体内的 def 不是类成员。
private _viperEnclosingClassKind: 'plain' | 'virtual' | 'novirtual' | undefined = undefined;
private _isParsingQuotedText = false;
private _isInFinallyBlock = false;
private _isInFinallyLoop = false;
private _futureImports = new Set<string>();
private _importedModules: ModuleImport[] = [];
private _containsWildcardImport = false;
private _assignmentExpressionsAllowed = true;
private _typingImportAliases: string[] = [];
private _typingSymbolAliases: Map<string, string> = new Map<string, string>();
private _maxChildDepthMap = new Map<number, number>();
private _hasTypeAnnotations = false;
parseSourceFile(fileContents: string, parseOptions: ParseOptions, diagSink: DiagnosticSink): ParseFileResults {
this._hasTypeAnnotations = false;
timingStats.tokenizeFileTime.timeOperation(() => {
this._startNewParse(fileContents, 0, fileContents.length, parseOptions, diagSink);
});
const moduleNode = ModuleNode.create({ start: 0, length: fileContents.length });
timingStats.parseFileTime.timeOperation(() => {
while (!this._atEof()) {
if (!this._consumeTokenIfType(TokenType.NewLine)) {
// Handle a common error case and try to recover.
const nextToken = this._peekToken();
if (nextToken.type === TokenType.Indent) {
this._getNextToken();
const indentToken = nextToken as IndentToken;
if (indentToken.isIndentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), indentToken);
} else {
this._addSyntaxError(LocMessage.unexpectedIndent(), nextToken);
}
}
const statement = this._parseStatement();
if (!statement) {
// Perform basic error recovery to get to the next line.
this._consumeTokensUntilType([TokenType.NewLine]);
} else {
statement.parent = moduleNode;
moduleNode.d.statements.push(statement);
}
}
}
});
assert(this._tokenizerOutput !== undefined);
return {
text: fileContents,
contentHash: hashString(fileContents),
parserOutput: {
parseTree: moduleNode,
importedModules: this._importedModules,
futureImports: this._futureImports,
containsWildcardImport: this._containsWildcardImport,
typingSymbolAliases: this._typingSymbolAliases,
hasTypeAnnotations: this._hasTypeAnnotations,
lines: this._tokenizerOutput!.lines,
},
tokenizerOutput: this._tokenizerOutput!,
};
}
parseTextExpression(
fileContents: string,
textOffset: number,
textLength: number,
parseOptions: ParseOptions,
parseTextMode: ParseTextMode.Expression,
initialParenDepth?: number,
typingSymbolAliases?: Map<string, string>
): ParseExpressionTextResults<ExpressionNode>;
parseTextExpression(
fileContents: string,
textOffset: number,
textLength: number,
parseOptions: ParseOptions,
parseTextMode: ParseTextMode.VariableAnnotation,
initialParenDepth?: number,
typingSymbolAliases?: Map<string, string>
): ParseExpressionTextResults<ExpressionNode>;
parseTextExpression(
fileContents: string,
textOffset: number,
textLength: number,
parseOptions: ParseOptions,
parseTextMode: ParseTextMode.FunctionAnnotation,
initialParenDepth?: number,
typingSymbolAliases?: Map<string, string>
): ParseExpressionTextResults<FunctionAnnotationNode>;
parseTextExpression(
fileContents: string,
textOffset: number,
textLength: number,
parseOptions: ParseOptions,
parseTextMode = ParseTextMode.Expression,
initialParenDepth = 0,
typingSymbolAliases?: Map<string, string>
): ParseExpressionTextResults<ExpressionNode | FunctionAnnotationNode> {
const diagSink = new DiagnosticSink();
this._startNewParse(fileContents, textOffset, textLength, parseOptions, diagSink, initialParenDepth);
if (typingSymbolAliases) {
this._typingSymbolAliases = new Map<string, string>(typingSymbolAliases);
}
let parseTree: ExpressionNode | FunctionAnnotationNode | undefined;
if (parseTextMode === ParseTextMode.VariableAnnotation) {
this._isParsingQuotedText = true;
parseTree = this._parseTypeAnnotation();
} else if (parseTextMode === ParseTextMode.FunctionAnnotation) {
this._isParsingQuotedText = true;
parseTree = this._parseFunctionTypeAnnotation();
} else {
const exprListResult = this._parseTestOrStarExpressionList(
/* allowAssignmentExpression */ false,
/* allowMultipleUnpack */ true
);
if (exprListResult.parseError) {
parseTree = exprListResult.parseError;
} else {
if (exprListResult.list.length === 0) {
this._addSyntaxError(LocMessage.expectedExpr(), this._peekToken());
}
parseTree = this._makeExpressionOrTuple(exprListResult, /* enclosedInParens */ false);
}
}
if (this._peekTokenType() === TokenType.NewLine) {
this._getNextToken();
}
if (!this._atEof()) {
this._addSyntaxError(LocMessage.unexpectedExprToken(), this._peekToken());
}
return {
parseTree,
lines: this._tokenizerOutput!.lines,
diagnostics: diagSink.fetchAndClear(),
};
}
private _startNewParse(
fileContents: string,
textOffset: number,
textLength: number,
parseOptions: ParseOptions,
diagSink: DiagnosticSink,
initialParenDepth = 0
) {
this._fileContents = fileContents;
this._parseOptions = parseOptions;
this._diagSink = diagSink;
// Tokenize the file contents.
const tokenizer = new Tokenizer();
this._tokenizerOutput = tokenizer.tokenize(
fileContents,
textOffset,
textLength,
initialParenDepth,
this._parseOptions.useNotebookMode
);
this._tokens = this._tokenizerOutput.tokens;
this._tokenCount = this._tokens.count;
this._tokenIndex = 0;
}
// stmt: simple_stmt | compound_stmt
// compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt
// | funcdef | classdef | decorated | async_stmt
private _parseStatement(): StatementNode | ErrorNode | undefined {
// Handle the errant condition of a dedent token here to provide
// better recovery.
if (this._consumeTokenIfType(TokenType.Dedent)) {
this._addSyntaxError(LocMessage.unexpectedUnindent(), this._peekToken());
}
switch (this._peekKeywordType()) {
case KeywordType.ViperModifier: {
// Viper 扩展:inline/export/extern 修饰关键字。
// 与 def(或其别名)搭配时跳过修饰关键字并正常解析函数;
// extern/export 还支持直接声明形式:`extern A(...)`(等价 `extern def A(...)`),
// 此时以下一个标识符作为函数名解析函数定义;
// 其余单独使用时报错。
const modifierToken = this._peekToken() as KeywordToken;
const modifierText = this._fileContents!.slice(
modifierToken.start,
modifierToken.start + modifierToken.length
);
// Viper 扩展:`virtual class X` / `novirtual class X`(类级 vtable 标记),
// `virtual def f(...)` / `novirtual def f(...)`(方法级 vtable 标记)。
// 语义等价 `@t.CVTable class X` / `@t.NoVTable class X`(见 t.vpi),
// 仅记录在节点上供编译器使用;类型层无额外语义。
// 必须在下方通用「修饰关键字 + def」分支之前,否则方法级修饰的
// 位置校验(virtual def 不得在 novirtual class 内 / 类外)会被跳过。
if (modifierText === 'virtual' || modifierText === 'novirtual') {
const nextKeywordType =
this._peekToken(1).type === TokenType.Keyword
? (this._peekToken(1) as KeywordToken).keywordType
: undefined;
if (nextKeywordType === KeywordType.Class) {
this._getNextToken();
return this._parseClassDef(/* decorators */ undefined, modifierText);
}
if (nextKeywordType === KeywordType.Def) {
this._validateViperVirtualMethodPlacement(modifierText, modifierToken);
this._getNextToken();
// 不传 defTokenOverride:`def` 关键字仍须由 _parseFunctionDef 消费。
return this._parseFunctionDef(
/* asyncToken */ undefined,
/* decorators */ undefined,
modifierText
);
}
this._addSyntaxError(
LocMessage.viperVirtualModifierRequiresClassOrDef().format({ name: modifierText }),
modifierToken
);
this._getNextToken();
return this._parseSimpleStatement();
}
if (
this._peekToken(1).type === TokenType.Keyword &&
(this._peekToken(1) as KeywordToken).keywordType === KeywordType.Def
) {
this._getNextToken();
return this._parseFunctionDef(/* asyncToken */ undefined, /* decorators */ undefined, modifierText);
}
if (
(modifierText === 'extern' || modifierText === 'export') &&
this._peekToken(1).type === TokenType.Identifier
) {
// extern/export 直接声明形式:`extern A(...)`。
// 消费修饰关键字后,下一个标识符即函数名;defToken 用修饰关键字
// 作为范围起点(diagnostic 定位)。
this._getNextToken();
return this._parseFunctionDef(
/* asyncToken */ undefined,
/* decorators */ undefined,
modifierText,
modifierToken
);
}
// Viper 扩展:`inline while cond:` / `inline for x in y:`(内联循环标记)。
// 内联是代码生成期的提示,类型层无额外语义,此处仅消费修饰关键字并
// 按普通 while / for 语句解析(高亮由 TextMate grammar 负责)。
if (modifierText === 'inline' && this._peekToken(1).type === TokenType.Keyword) {
const nextKeywordType = (this._peekToken(1) as KeywordToken).keywordType;
if (nextKeywordType === KeywordType.While) {
this._getNextToken();
return this._parseWhileStatement();
}
if (nextKeywordType === KeywordType.For) {
this._getNextToken();
return this._parseForStatement();
}
}
this._addSyntaxError(
LocMessage.viperModifierRequiresDef().format({ name: modifierText }),
modifierToken
);
this._getNextToken();
return this._parseSimpleStatement();
}
case KeywordType.If:
return this._parseIfStatement();
case KeywordType.While:
return this._parseWhileStatement();
case KeywordType.For:
return this._parseForStatement();
case KeywordType.Try:
return this._parseTryStatement();
case KeywordType.With:
return this._parseWithStatement();
case KeywordType.Def:
return this._parseFunctionDef();
case KeywordType.Define:
// Viper 扩展:`define NAME: 类型 = 值` / `define NAME = 值` 为编译期常量声明
// (值必须能存入声明的类型位宽,见 checker 的整数字面量位宽校验)。
// 与 `define f(...)`(宏函数,等价 def)区分:常量声明的名字后跟 ':' 或 '='。
if (this._isViperDefineConstDeclaration()) {
this._getNextToken();
return this._parseSimpleStatement();
}
// Viper 扩展:define 宏函数(与 def 语法一致,语义为编译期宏展开)。
return this._parseFunctionDef();
case KeywordType.Class:
return this._parseClassDef();
case KeywordType.Async:
return this._parseAsyncStatement();
case KeywordType.Match: {
// Match is considered a "soft" keyword, so we will treat
// it as an identifier if it is followed by an unexpected
// token.
const peekToken = this._peekToken(1);
let isInvalidMatchToken = false;
if (
peekToken.type === TokenType.Colon ||
peekToken.type === TokenType.Semicolon ||
peekToken.type === TokenType.Comma ||
peekToken.type === TokenType.Dot ||
peekToken.type === TokenType.NewLine ||
peekToken.type === TokenType.EndOfStream
) {
isInvalidMatchToken = true;
} else if (peekToken.type === TokenType.Operator) {
const operatorToken = peekToken as OperatorToken;
if (
operatorToken.operatorType !== OperatorType.Multiply &&
operatorToken.operatorType !== OperatorType.Add &&
operatorToken.operatorType !== OperatorType.BitwiseInvert &&
operatorToken.operatorType !== OperatorType.Subtract
) {
isInvalidMatchToken = true;
}
}
if (!isInvalidMatchToken) {
// Try to parse the match statement. If it doesn't appear to
// be a match statement, treat as a non-keyword and reparse.
const matchStatement = this._parseMatchStatement();
if (matchStatement) {
return matchStatement;
}
}
break;
}
}
if (this._peekOperatorType() === OperatorType.MatrixMultiply) {
return this._parseDecorated();
}
return this._parseSimpleStatement();
}
// async_stmt: 'async' (funcdef | with_stmt | for_stmt)
private _parseAsyncStatement(): StatementNode | undefined {
const asyncToken = this._getKeywordToken(KeywordType.Async);
switch (this._peekKeywordType()) {
case KeywordType.Def:
return this._parseFunctionDef(asyncToken);
case KeywordType.With:
return this._parseWithStatement(asyncToken);
case KeywordType.For:
return this._parseForStatement(asyncToken);
}
this._addSyntaxError(LocMessage.unexpectedAsyncToken(), asyncToken);
return undefined;
}
// lazy_import_stmt: "lazy" import_stmt | "lazy" from_import_stmt
private _parseLazyImportStatement(): ImportNode | ImportFromNode {
const lazyToken = this._getKeywordToken(KeywordType.Lazy);
if (!this._parseOptions.isStubFile && PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_15)) {
this._addSyntaxError(LocMessage.lazyImportIllegal(), lazyToken);
}
const nextToken = this._peekToken();
let node: ImportNode | ImportFromNode;
if (nextToken.type === TokenType.Keyword && (nextToken as KeywordToken).keywordType === KeywordType.From) {
node = this._parseFromStatement();
} else {
node = this._parseImportStatement();
}
node.d.isLazy = true;
node.d.lazyToken = lazyToken as KeywordToken;
extendRange(node, lazyToken);
// PEP 810 disallows wildcard imports with 'lazy'.
if (node.nodeType === ParseNodeType.ImportFrom && node.d.isWildcardImport) {
this._addSyntaxError(LocMessage.lazyImportWildcardIllegal(), node.d.wildcardToken ?? lazyToken);
}
return node;
}
// type_alias_stmt: "type" name [type_param_seq] = expr
private _parseTypeAliasStatement(): TypeAliasNode {
const typeToken = this._getKeywordToken(KeywordType.Type);
const nameToken = this._getTokenIfIdentifier();
assert(nameToken !== undefined);
const name = NameNode.create(nameToken);
let typeParameters: TypeParameterListNode | undefined;
if (this._peekToken().type === TokenType.OpenBracket) {
typeParameters = this._parseTypeParameterList();
}
const assignToken = this._peekToken();
if (
assignToken.type !== TokenType.Operator ||
(assignToken as OperatorToken).operatorType !== OperatorType.Assign
) {
this._addSyntaxError(LocMessage.expectedEquals(), assignToken);
} else {
this._getNextToken();
}
const wasParsingTypeAnnotation = this._isParsingTypeAnnotation;
this._isParsingTypeAnnotation = true;
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);
}
// type_param_seq: '[' (type_param ',')+ ']'
private _parseTypeParameterList(): TypeParameterListNode {
const typeVariableNodes: TypeParameterNode[] = [];
const openBracketToken = this._getNextToken();
assert(openBracketToken.type === TokenType.OpenBracket);
while (true) {
const firstToken = this._peekToken();
if (firstToken.type === TokenType.CloseBracket) {
if (typeVariableNodes.length === 0) {
this._addSyntaxError(LocMessage.typeParametersMissing(), this._peekToken());
}
break;
}
const typeVarNode = this._parseTypeParameter();
if (!typeVarNode) {
break;
}
typeVariableNodes.push(typeVarNode);
if (!this._consumeTokenIfType(TokenType.Comma)) {
break;
}
}
const closingToken = this._peekToken();
if (closingToken.type !== TokenType.CloseBracket) {
this._addSyntaxError(LocMessage.expectedCloseBracket(), this._peekToken());
this._consumeTokensUntilType([TokenType.NewLine, TokenType.CloseBracket, TokenType.Colon]);
} else {
this._getNextToken();
}
return TypeParameterListNode.create(openBracketToken, closingToken, typeVariableNodes);
}
// type_param: ['*' | '**'] NAME [':' bound_expr] ['=' default_expr]
private _parseTypeParameter(): TypeParameterNode | undefined {
let typeParamCategory = TypeParamKind.TypeVar;
if (this._consumeTokenIfOperator(OperatorType.Multiply)) {
typeParamCategory = TypeParamKind.TypeVarTuple;
} else if (this._consumeTokenIfOperator(OperatorType.Power)) {
typeParamCategory = TypeParamKind.ParamSpec;
}
const nameToken = this._getTokenIfIdentifier();
if (!nameToken) {
this._addSyntaxError(LocMessage.expectedTypeParameterName(), this._peekToken());
return undefined;
}
const name = NameNode.create(nameToken);
let boundExpression: ExpressionNode | undefined;
if (this._consumeTokenIfType(TokenType.Colon)) {
boundExpression = this._parseExpression(/* allowUnpack */ false);
if (typeParamCategory !== TypeParamKind.TypeVar) {
this._addSyntaxError(LocMessage.typeParameterBoundNotAllowed(), boundExpression);
}
}
let defaultExpression: ExpressionNode | undefined;
if (this._consumeTokenIfOperator(OperatorType.Assign)) {
defaultExpression = this._parseExpression(
/* allowUnpack */ typeParamCategory === TypeParamKind.TypeVarTuple
);
if (
!this._parseOptions.isStubFile &&
PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_13)
) {
this._addSyntaxError(LocMessage.typeVarDefaultIllegal(), defaultExpression);
}
}
return TypeParameterNode.create(name, typeParamCategory, boundExpression, defaultExpression);
}
// match_stmt: "match" subject_expr ':' NEWLINE INDENT case_block+ DEDENT
// subject_expr:
// | star_named_expression ',' star_named_expressions?
// | named_expression
private _parseMatchStatement(): MatchNode | undefined {
// Parse the subject expression with errors suppressed. If it's not
// followed by a colon, we'll assume this is not a match statement.
// We need to do this because "match" is considered a soft keyword,
// and we need to distinguish between "match(2)" and "match (2):"
// and between "match[2]" and "match [2]:"
let smellsLikeMatchStatement = false;
this._suppressErrors(() => {
const curTokenIndex = this._tokenIndex;
this._getKeywordToken(KeywordType.Match);
const expression = this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ true,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingPatternSubject,
() => LocMessage.expectedReturnExpr()
);
smellsLikeMatchStatement =
expression.nodeType !== ParseNodeType.Error && this._peekToken().type === TokenType.Colon;
// Set the token index back to the start.
this._tokenIndex = curTokenIndex;
});
if (!smellsLikeMatchStatement) {
return undefined;
}
const matchToken = this._getKeywordToken(KeywordType.Match);
const subjectExpression = this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ true,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingPatternSubject,
() => LocMessage.expectedReturnExpr()
);
const matchNode = MatchNode.create(matchToken, subjectExpression);
const nextToken = this._peekToken();
if (!this._consumeTokenIfType(TokenType.Colon)) {
this._addSyntaxError(LocMessage.expectedColon(), nextToken);
// Try to perform parse recovery by consuming tokens until
// we find the end of the line.
if (this._consumeTokensUntilType([TokenType.NewLine, TokenType.Colon])) {
this._getNextToken();
}
} else {
extendRange(matchNode, nextToken);
if (!this._consumeTokenIfType(TokenType.NewLine)) {
this._addSyntaxError(LocMessage.expectedNewline(), nextToken);
} else {
const possibleIndent = this._peekToken();
if (!this._consumeTokenIfType(TokenType.Indent)) {
this._addSyntaxError(LocMessage.expectedIndentedBlock(), this._peekToken());
} else {
const indentToken = possibleIndent as IndentToken;
if (indentToken.isIndentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), indentToken);
}
}
while (true) {
// Handle a common error here and see if we can recover.
const possibleUnexpectedIndent = this._peekToken();
if (possibleUnexpectedIndent.type === TokenType.Indent) {
this._getNextToken();
const indentToken = possibleUnexpectedIndent as IndentToken;
if (indentToken.isIndentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), indentToken);
} else {
this._addSyntaxError(LocMessage.unexpectedIndent(), possibleUnexpectedIndent);
}
}
const caseStatement = this._parseCaseStatement();
if (!caseStatement) {
// Perform basic error recovery to get to the next line.
if (this._consumeTokensUntilType([TokenType.NewLine, TokenType.Colon])) {
this._getNextToken();
}
} else {
caseStatement.parent = matchNode;
matchNode.d.cases.push(caseStatement);
}
const dedentToken = this._peekToken() as DedentToken;
if (this._consumeTokenIfType(TokenType.Dedent)) {
if (!dedentToken.matchesIndent) {
this._addSyntaxError(LocMessage.inconsistentIndent(), dedentToken);
}
if (dedentToken.isDedentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), dedentToken);
}
break;
}
if (this._peekTokenType() === TokenType.EndOfStream) {
break;
}
}
}
if (matchNode.d.cases.length > 0) {
extendRange(matchNode, matchNode.d.cases[matchNode.d.cases.length - 1]);
} else {
this._addSyntaxError(LocMessage.zeroCaseStatementsFound(), matchToken);
}
}
// This feature requires Python 3.10.
if (PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_10)) {
this._addSyntaxError(LocMessage.matchIncompatible(), matchToken);
}
// Validate that only the last entry uses an irrefutable pattern.
for (let i = 0; i < matchNode.d.cases.length - 1; i++) {
const caseNode = matchNode.d.cases[i];
if (!caseNode.d.guardExpr && caseNode.d.isIrrefutable) {
this._addSyntaxError(LocMessage.casePatternIsIrrefutable(), caseNode.d.pattern);
}
}
return matchNode;
}
// case_block: "case" patterns [guard] ':' block
// patterns: sequence_pattern | as_pattern
// guard: 'if' named_expression
private _parseCaseStatement(): CaseNode | undefined {
const caseToken = this._peekToken();
if (!this._consumeTokenIfKeyword(KeywordType.Case)) {
this._addSyntaxError(LocMessage.expectedCase(), caseToken);
return undefined;
}
const patternList = this._parsePatternSequence();
let casePattern: PatternAtomNode;
if (patternList.parseError) {
casePattern = patternList.parseError;
} else if (patternList.list.length === 0) {
this._addSyntaxError(LocMessage.expectedPatternExpr(), this._peekToken());
casePattern = ErrorNode.create(caseToken, ErrorExpressionCategory.MissingPattern);
} else if (patternList.list.length === 1 && !patternList.trailingComma) {
const pattern = patternList.list[0].d.orPatterns[0];
if (pattern.nodeType === ParseNodeType.PatternCapture && pattern.d.isStar) {
casePattern = PatternSequenceNode.create(patternList.list[0], patternList.list);
} else {
casePattern = patternList.list[0];
}
} else {
casePattern = PatternSequenceNode.create(patternList.list[0], patternList.list);
}
if (casePattern.nodeType !== ParseNodeType.Error) {
const globalNameMap = new Map<string, NameNode>();
const localNameMap = new Map<string, NameNode>();
this._reportDuplicatePatternCaptureTargets(casePattern, globalNameMap, localNameMap);
}
let guardExpression: ExpressionNode | undefined;
if (this._consumeTokenIfKeyword(KeywordType.If)) {
guardExpression = this._parseTestExpression(/* allowAssignmentExpression */ true);
}
const suite = this._parseSuite(this._isInFunction);
return CaseNode.create(caseToken, casePattern, this._isPatternIrrefutable(casePattern), guardExpression, suite);
}
// PEP 634 defines the concept of an "irrefutable" pattern - a pattern that
// will always be matched.
private _isPatternIrrefutable(node: PatternAtomNode): boolean {
if (node.nodeType === ParseNodeType.PatternCapture) {
return true;
}
if (node.nodeType === ParseNodeType.PatternAs) {
return node.d.orPatterns.some((pattern) => this._isPatternIrrefutable(pattern));
}
return false;
}
// Reports any situations where a capture target (a variable that receives part of a pattern)
// appears twice within the same pattern. This is complicated by the fact that duplicate targets
// are allowed in separate "or" clauses, so we need to track the targets we've seen globally
// as well as the targets we've seen locally within the current "or" clause.
private _reportDuplicatePatternCaptureTargets(
node: PatternAtomNode,
globalNameMap: Map<string, NameNode>,
localNameMap: Map<string, NameNode>
) {
const reportTargetIfDuplicate = (nameNode: NameNode) => {
if (globalNameMap.has(nameNode.d.value) || localNameMap.has(nameNode.d.value)) {
this._addSyntaxError(
LocMessage.duplicateCapturePatternTarget().format({
name: nameNode.d.value,
}),
nameNode
);
} else {
localNameMap.set(nameNode.d.value, nameNode);
}
};
switch (node.nodeType) {
case ParseNodeType.PatternSequence: {
node.d.entries.forEach((subpattern) => {
this._reportDuplicatePatternCaptureTargets(subpattern, globalNameMap, localNameMap);
});
break;
}
case ParseNodeType.PatternClass: {
node.d.args.forEach((arg) => {
this._reportDuplicatePatternCaptureTargets(arg.d.pattern, globalNameMap, localNameMap);
});
break;
}
case ParseNodeType.PatternAs: {
if (node.d.target) {
reportTargetIfDuplicate(node.d.target);
}
const orLocalNameMaps = node.d.orPatterns.map((subpattern) => {
const orLocalNameMap = new Map<string, NameNode>();
this._reportDuplicatePatternCaptureTargets(subpattern, localNameMap, orLocalNameMap);
return orLocalNameMap;
});
const combinedLocalOrNameMap = new Map<string, NameNode>();
orLocalNameMaps.forEach((orLocalNameMap) => {
orLocalNameMap.forEach((node) => {
if (!combinedLocalOrNameMap.has(node.d.value)) {
combinedLocalOrNameMap.set(node.d.value, node);
reportTargetIfDuplicate(node);
}
});
});
break;
}
case ParseNodeType.PatternCapture: {
if (!node.d.isWildcard) {
reportTargetIfDuplicate(node.d.target);
}
break;
}
case ParseNodeType.PatternMapping: {
node.d.entries.forEach((mapEntry) => {
if (mapEntry.nodeType === ParseNodeType.PatternMappingExpandEntry) {
reportTargetIfDuplicate(mapEntry.d.target);
} else {
this._reportDuplicatePatternCaptureTargets(mapEntry.d.keyPattern, globalNameMap, localNameMap);
this._reportDuplicatePatternCaptureTargets(
mapEntry.d.valuePattern,
globalNameMap,
localNameMap
);
}
});
break;
}
case ParseNodeType.PatternLiteral:
case ParseNodeType.PatternValue:
case ParseNodeType.Error: {
break;
}
}
}
private _getPatternTargetNames(node: PatternAtomNode, nameSet: Set<string>): void {
switch (node.nodeType) {
case ParseNodeType.PatternSequence: {
node.d.entries.forEach((subpattern) => {
this._getPatternTargetNames(subpattern, nameSet);
});
break;
}
case ParseNodeType.PatternClass: {
node.d.args.forEach((arg) => {
this._getPatternTargetNames(arg.d.pattern, nameSet);
});
break;
}
case ParseNodeType.PatternAs: {
if (node.d.target) {
nameSet.add(node.d.target.d.value);
}
node.d.orPatterns.forEach((subpattern) => {
this._getPatternTargetNames(subpattern, nameSet);
});
break;
}
case ParseNodeType.PatternCapture: {
if (!node.d.isWildcard) {
nameSet.add(node.d.target.d.value);
}
break;
}
case ParseNodeType.PatternMapping: {
node.d.entries.forEach((mapEntry) => {
if (mapEntry.nodeType === ParseNodeType.PatternMappingExpandEntry) {
nameSet.add(mapEntry.d.target.d.value);
} else {
this._getPatternTargetNames(mapEntry.d.keyPattern, nameSet);
this._getPatternTargetNames(mapEntry.d.valuePattern, nameSet);
}
});
break;
}
case ParseNodeType.PatternLiteral:
case ParseNodeType.PatternValue:
case ParseNodeType.Error: {
break;
}
}
}
private _parsePatternSequence() {
const patternList = this._parseExpressionListGeneric(() => this._parsePatternAs());
// Check for more than one star entry.
const starEntries = patternList.list.filter(
(entry) =>
entry.d.orPatterns.length === 1 &&
entry.d.orPatterns[0].nodeType === ParseNodeType.PatternCapture &&
entry.d.orPatterns[0].d.isStar
);
if (starEntries.length > 1) {
this._addSyntaxError(LocMessage.duplicateStarPattern(), starEntries[1].d.orPatterns[0]);
}
return patternList;
}
// as_pattern: or_pattern ['as' NAME]
// or_pattern: '|'.pattern_atom+
private _parsePatternAs(): PatternAsNode {
const orPatterns: PatternAtomNode[] = [];
while (true) {
const patternAtom = this._parsePatternAtom();
orPatterns.push(patternAtom);
if (!this._consumeTokenIfOperator(OperatorType.BitwiseOr)) {
break;
}
}
if (orPatterns.length > 1) {
// Star patterns cannot be ORed with other patterns.
orPatterns.forEach((patternAtom) => {
if (patternAtom.nodeType === ParseNodeType.PatternCapture && patternAtom.d.isStar) {
this._addSyntaxError(LocMessage.starPatternInOrPattern(), patternAtom);
}
});
}
let target: NameNode | undefined;
if (this._consumeTokenIfKeyword(KeywordType.As)) {
const nameToken = this._getTokenIfIdentifier();
if (nameToken) {
target = NameNode.create(nameToken);
} else {
this._addSyntaxError(LocMessage.expectedNameAfterAs(), this._peekToken());
}
}
// Star patterns cannot be used with AS pattern.
if (
target &&
orPatterns.length === 1 &&
orPatterns[0].nodeType === ParseNodeType.PatternCapture &&
orPatterns[0].d.isStar
) {
this._addSyntaxError(LocMessage.starPatternInAsPattern(), orPatterns[0]);
}
// Validate that irrefutable patterns are not in any entries other than the last.
orPatterns.forEach((orPattern, index) => {
if (index < orPatterns.length - 1 && this._isPatternIrrefutable(orPattern)) {
this._addSyntaxError(LocMessage.orPatternIrrefutable(), orPattern);
}
});
// Validate that all bound variables are the same within all or patterns.
const fullNameSet = new Set<string>();
orPatterns.forEach((orPattern) => {
this._getPatternTargetNames(orPattern, fullNameSet);
});
orPatterns.forEach((orPattern) => {
const localNameSet = new Set<string>();
this._getPatternTargetNames(orPattern, localNameSet);
if (localNameSet.size < fullNameSet.size) {
const missingNames = Array.from(fullNameSet.keys()).filter((name) => !localNameSet.has(name));
const diag = new DiagnosticAddendum();
diag.addMessage(
LocAddendum.orPatternMissingName().format({
name: missingNames.map((name) => `"${name}"`).join(', '),
})
);
this._addSyntaxError(LocMessage.orPatternMissingName() + diag.getString(), orPattern);
}
});
return PatternAsNode.create(orPatterns, target);
}
// pattern_atom:
// | literal_pattern
// | name_or_attr
// | '(' as_pattern ')'
// | '[' [sequence_pattern] ']'
// | '(' [sequence_pattern] ')'
// | '{' [items_pattern] '}'
// | name_or_attr '(' [pattern_arguments ','?] ')'
// name_or_attr: attr | NAME
// attr: name_or_attr '.' NAME
// sequence_pattern: ','.maybe_star_pattern+ ','?
// maybe_star_pattern: '*' NAME | pattern
// items_pattern: ','.key_value_pattern+ ','?
private _parsePatternAtom(): PatternAtomNode {
const patternLiteral = this._parsePatternLiteral();
if (patternLiteral) {
return patternLiteral;
}
const patternCaptureOrValue = this._parsePatternCaptureOrValue();
if (patternCaptureOrValue) {
const openParenToken = this._peekToken();
if (
patternCaptureOrValue.nodeType === ParseNodeType.Error ||
!this._consumeTokenIfType(TokenType.OpenParenthesis)
) {
return patternCaptureOrValue;
}
const args = this._parseClassPatternArgList();
const classNameExpr =
patternCaptureOrValue.nodeType === ParseNodeType.PatternCapture
? patternCaptureOrValue.d.target
: patternCaptureOrValue.d.expr;
const classPattern = PatternClassNode.create(classNameExpr, args);
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
this._addSyntaxError(LocMessage.expectedCloseParen(), openParenToken);
// Consume the remainder of tokens on the line for error
// recovery.
this._consumeTokensUntilType([TokenType.NewLine]);
// Extend the node's range to include the rest of the line.
// This helps the signatureHelpProvider.
extendRange(classPattern, this._peekToken());
}
return classPattern;
}
const nextToken = this._peekToken();
const nextOperator = this._peekOperatorType();
if (nextOperator === OperatorType.Multiply) {
const starToken = this._getNextToken();
const identifierToken = this._getTokenIfIdentifier();
if (!identifierToken) {
this._addSyntaxError(LocMessage.expectedIdentifier(), this._peekToken());
return ErrorNode.create(starToken, ErrorExpressionCategory.MissingExpression);
} else {
return PatternCaptureNode.create(NameNode.create(identifierToken), starToken);
}
}
if (nextToken.type === TokenType.OpenParenthesis || nextToken.type === TokenType.OpenBracket) {
const startToken = this._getNextToken();
const patternList = this._parsePatternSequence();
let casePattern: PatternAtomNode;
if (patternList.parseError) {
casePattern = patternList.parseError;
} else if (
patternList.list.length === 1 &&
!patternList.trailingComma &&
startToken.type === TokenType.OpenParenthesis
) {
const pattern = patternList.list[0].d.orPatterns[0];
if (pattern.nodeType === ParseNodeType.PatternCapture && pattern.d.isStar) {
casePattern = PatternSequenceNode.create(startToken, patternList.list);
} else {
casePattern = patternList.list[0];
}
extendRange(casePattern, nextToken);
} else {
casePattern = PatternSequenceNode.create(startToken, patternList.list);
}
const endToken = this._peekToken();
if (
this._consumeTokenIfType(
nextToken.type === TokenType.OpenParenthesis ? TokenType.CloseParenthesis : TokenType.CloseBracket
)
) {
extendRange(casePattern, endToken);
} else {
this._addSyntaxError(
nextToken.type === TokenType.OpenParenthesis
? LocMessage.expectedCloseParen()
: LocMessage.expectedCloseBracket(),
nextToken
);
this._consumeTokensUntilType([
TokenType.Colon,
nextToken.type === TokenType.OpenParenthesis ? TokenType.CloseParenthesis : TokenType.CloseBracket,
]);
}
return casePattern;
} else if (nextToken.type === TokenType.OpenCurlyBrace) {
const firstToken = this._getNextToken();
const mappingPattern = this._parsePatternMapping(firstToken);
const lastToken = this._peekToken();
if (this._consumeTokenIfType(TokenType.CloseCurlyBrace)) {
extendRange(mappingPattern, lastToken);
} else {
this._addSyntaxError(LocMessage.expectedCloseBrace(), nextToken);
this._consumeTokensUntilType([TokenType.Colon, TokenType.CloseCurlyBrace]);
}
return mappingPattern;
}
return this._handleExpressionParseError(
ErrorExpressionCategory.MissingPattern,
LocMessage.expectedPatternExpr()
);
}
// pattern_arguments:
// | positional_patterns [',' keyword_patterns]
// | keyword_patterns
// positional_patterns: ','.as_pattern+
// keyword_patterns: ','.keyword_pattern+
private _parseClassPatternArgList(): PatternClassArgumentNode[] {
const argList: PatternClassArgumentNode[] = [];
let sawKeywordArg = false;
while (true) {
const nextTokenType = this._peekTokenType();
if (
nextTokenType === TokenType.CloseParenthesis ||
nextTokenType === TokenType.NewLine ||
nextTokenType === TokenType.EndOfStream
) {
break;
}
const arg = this._parseClassPatternArgument();
if (arg.d.name) {
sawKeywordArg = true;
} else if (sawKeywordArg && !arg.d.name) {
this._addSyntaxError(LocMessage.positionArgAfterNamedArg(), arg);
}
argList.push(arg);
if (!this._consumeTokenIfType(TokenType.Comma)) {
break;
}
}
return argList;
}
// keyword_pattern: NAME '=' as_pattern
private _parseClassPatternArgument(): PatternClassArgumentNode {
const firstToken = this._peekToken();
const secondToken = this._peekToken(1);
let keywordName: NameNode | undefined;
if (
(firstToken.type === TokenType.Identifier || firstToken.type === TokenType.Keyword) &&
secondToken.type === TokenType.Operator &&
(secondToken as OperatorToken).operatorType === OperatorType.Assign
) {
const classNameToken = this._getTokenIfIdentifier();
if (classNameToken !== undefined) {
keywordName = NameNode.create(classNameToken);
this._getNextToken();
}
}
const pattern = this._parsePatternAs();
return PatternClassArgumentNode.create(pattern, keywordName);
}
// literal_pattern:
// | signed_number
// | signed_number '+' NUMBER
// | signed_number '-' NUMBER
// | strings
// | 'None'
// | 'True'
// | 'False'
private _parsePatternLiteral(): PatternLiteralNode | undefined {
const nextToken = this._peekToken();
const nextOperator = this._peekOperatorType();
if (nextToken.type === TokenType.Number || nextOperator === OperatorType.Subtract) {
return this._parsePatternLiteralNumber();
}
if (nextToken.type === TokenType.String) {
const stringList = this._parseAtom() as StringListNode;
assert(stringList.nodeType === ParseNodeType.StringList);
// Check for f-strings, which are not allowed.
stringList.d.strings.forEach((stringAtom) => {
if (stringAtom.nodeType === ParseNodeType.FormatString) {
this._addSyntaxError(LocMessage.formatStringInPattern(), stringAtom);
}
});
return PatternLiteralNode.create(stringList);
}
if (nextToken.type === TokenType.Keyword) {
const keywordToken = nextToken as KeywordToken;
if (
keywordToken.keywordType === KeywordType.False ||
keywordToken.keywordType === KeywordType.True ||
keywordToken.keywordType === KeywordType.None
) {
return PatternLiteralNode.create(this._parseAtom());
}
}
return undefined;
}
// signed_number: NUMBER | '-' NUMBER
private _parsePatternLiteralNumber(): PatternLiteralNode {
const expression = this._parseArithmeticExpression();
let realValue: ExpressionNode | undefined;
let imagValue: ExpressionNode | undefined;
if (expression.nodeType === ParseNodeType.BinaryOperation) {
if (expression.d.operator === OperatorType.Subtract || expression.d.operator === OperatorType.Add) {
realValue = expression.d.leftExpr;
imagValue = expression.d.rightExpr;
}
} else {
realValue = expression;
}
if (realValue) {
if (realValue.nodeType === ParseNodeType.UnaryOperation && realValue.d.operator === OperatorType.Subtract) {
realValue = realValue.d.expr;
}
if (realValue.nodeType !== ParseNodeType.Number || (imagValue !== undefined && realValue.d.isImaginary)) {
this._addSyntaxError(LocMessage.expectedComplexNumberLiteral(), expression);
imagValue = undefined;
}
}
if (imagValue) {
if (imagValue.nodeType === ParseNodeType.UnaryOperation && imagValue.d.operator === OperatorType.Subtract) {
imagValue = imagValue.d.expr;
}
if (imagValue.nodeType !== ParseNodeType.Number || !imagValue.d.isImaginary) {
this._addSyntaxError(LocMessage.expectedComplexNumberLiteral(), expression);
}
}
return PatternLiteralNode.create(expression);
}
private _parsePatternMapping(firstToken: Token): PatternMappingNode | ErrorNode {
const itemList = this._parseExpressionListGeneric(() => this._parsePatternMappingItem());
if (itemList.list.length > 0) {
// Verify there's at most one ** entry.
const starStarEntries = itemList.list.filter(
(entry) => entry.nodeType === ParseNodeType.PatternMappingExpandEntry
);
if (starStarEntries.length > 1) {
this._addSyntaxError(LocMessage.duplicateStarStarPattern(), starStarEntries[1]);
}
return PatternMappingNode.create(firstToken, itemList.list);
}
return itemList.parseError || ErrorNode.create(this._peekToken(), ErrorExpressionCategory.MissingPattern);
}
// key_value_pattern:
// | (literal_pattern | attr) ':' as_pattern
// | '**' NAME
private _parsePatternMappingItem(): PatternMappingEntryNode | ErrorNode {
let keyExpression: PatternLiteralNode | PatternValueNode | ErrorNode | undefined;
const doubleStar = this._peekToken();
if (this._consumeTokenIfOperator(OperatorType.Power)) {
const identifierToken = this._getTokenIfIdentifier();
if (!identifierToken) {
this._addSyntaxError(LocMessage.expectedIdentifier(), this._peekToken());
return ErrorNode.create(this._peekToken(), ErrorExpressionCategory.MissingPattern);
}
const nameNode = NameNode.create(identifierToken);
if (identifierToken.value === '_') {
this._addSyntaxError(LocMessage.starStarWildcardNotAllowed(), nameNode);
}
return PatternMappingExpandEntryNode.create(doubleStar, nameNode);
}
const patternLiteral = this._parsePatternLiteral();
if (patternLiteral) {
keyExpression = patternLiteral;
} else {
const patternCaptureOrValue = this._parsePatternCaptureOrValue();
if (patternCaptureOrValue) {
if (patternCaptureOrValue.nodeType === ParseNodeType.PatternValue) {
keyExpression = patternCaptureOrValue;
} else {
this._addSyntaxError(LocMessage.expectedPatternValue(), patternCaptureOrValue);
keyExpression = ErrorNode.create(this._peekToken(), ErrorExpressionCategory.MissingPattern);
}
}
}
if (!keyExpression) {
this._addSyntaxError(LocMessage.expectedPatternExpr(), this._peekToken());
keyExpression = ErrorNode.create(this._peekToken(), ErrorExpressionCategory.MissingPattern);
}
let valuePattern: PatternAtomNode | undefined;
if (!this._consumeTokenIfType(TokenType.Colon)) {
this._addSyntaxError(LocMessage.expectedColon(), this._peekToken());
valuePattern = ErrorNode.create(this._peekToken(), ErrorExpressionCategory.MissingPattern);
} else {
valuePattern = this._parsePatternAs();
}
return PatternMappingKeyEntryNode.create(keyExpression, valuePattern);
}
private _parsePatternCaptureOrValue(): PatternCaptureNode | PatternValueNode | ErrorNode | undefined {
const nextToken = this._peekToken();
if (nextToken.type === TokenType.Identifier || nextToken.type === TokenType.Keyword) {
let nameOrMember: NameNode | MemberAccessNode | undefined;
while (true) {
const identifierToken = this._getTokenIfIdentifier();
if (identifierToken) {
const nameNode = NameNode.create(identifierToken);
nameOrMember = nameOrMember ? MemberAccessNode.create(nameOrMember, nameNode) : nameNode;
} else {
this._addSyntaxError(LocMessage.expectedIdentifier(), this._peekToken());
break;
}
if (!this._consumeTokenIfType(TokenType.Dot)) {
break;
}
}
if (!nameOrMember) {
this._addSyntaxError(LocMessage.expectedIdentifier(), this._peekToken());
return ErrorNode.create(this._peekToken(), ErrorExpressionCategory.MissingPattern);
}
if (nameOrMember.nodeType === ParseNodeType.MemberAccess) {
return PatternValueNode.create(nameOrMember);
}
return PatternCaptureNode.create(nameOrMember);
}
return undefined;
}
// if_stmt: 'if' test_suite ('elif' test_suite)* ['else' suite]
// test_suite: test suite
// test: or_test ['if' or_test 'else' test] | lambdef
private _parseIfStatement(keywordType: KeywordType.If | KeywordType.Elif = KeywordType.If): IfNode {
const ifOrElifToken = this._getKeywordToken(keywordType);
const test = this._parseTestExpression(/* allowAssignmentExpression */ true);
const suite = this._parseSuite(this._isInFunction);
const ifNode = IfNode.create(ifOrElifToken, test, suite);
if (this._consumeTokenIfKeyword(KeywordType.Else)) {
ifNode.d.elseSuite = this._parseSuite(this._isInFunction);
ifNode.d.elseSuite.parent = ifNode;
extendRange(ifNode, ifNode.d.elseSuite);
} else if (this._peekKeywordType() === KeywordType.Elif) {
// Recursively handle an "elif" statement.
ifNode.d.elseSuite = this._parseIfStatement(KeywordType.Elif);
ifNode.d.elseSuite.parent = ifNode;
extendRange(ifNode, ifNode.d.elseSuite);
}
return ifNode;
}
private _parseExceptSuite<T>(isExceptionGroup: boolean, callback: () => T): T {
const wasInExceptionGroup = this._isInExceptionGroup;
if (isExceptionGroup) {
this._isInExceptionGroup = true;
}
const result = callback();
this._isInExceptionGroup = wasInExceptionGroup;
return result;
}
private _parseLoopSuite(): SuiteNode {
const wasInLoop = this._isInLoop;
const wasInExceptionGroup = this._isInExceptionGroup;
this._isInExceptionGroup = false;
this._isInLoop = true;
// Record the fact that we are no longer in a finally block
// that is contained within a loop. A loop within the finally
// block resets this. See PEP 765 for details.
const wasInFinallyLoop = this._isInFinallyLoop;
this._isInFinallyLoop = false;
let typeComment: StringToken | undefined;
const suite = this._parseSuite(this._isInFunction, /* skipBody */ false, () => {
const comment = this._getTypeAnnotationCommentText();
if (comment) {
typeComment = comment;
}
});
this._isInLoop = wasInLoop;
this._isInFinallyLoop = wasInFinallyLoop;
this._isInExceptionGroup = wasInExceptionGroup;
if (typeComment) {
suite.d.typeComment = typeComment;
}
return suite;
}
// suite: ':' (simple_stmt | NEWLINE INDENT stmt+ DEDENT)
// allowTrailingIndent(Viper 扩展):允许 simple_stmt 之后再接 NEWLINE INDENT stmt+ DEDENT,
// 用于 `enum X: a, b, c` 后换行 + TAB 续写枚举成员。
private _parseSuite(
isFunction = false,
skipBody = false,
postColonCallback?: () => void,
allowTrailingIndent = false
): SuiteNode {
const nextToken = this._peekToken();
const suite = SuiteNode.create(nextToken);
if (!this._consumeTokenIfType(TokenType.Colon)) {
this._addSyntaxError(LocMessage.expectedColon(), nextToken);
// Try to perform parse recovery by consuming tokens.
if (this._consumeTokensUntilType([TokenType.NewLine, TokenType.Colon])) {
if (this._peekTokenType() === TokenType.Colon) {
this._getNextToken();
} else if (this._peekToken(1).type !== TokenType.Indent) {
// Bail so we resume the at the next statement.
// We can't parse as a simple statement as we've skipped all but the newline.
this._getNextToken();
return suite;
}
}
}
if (skipBody) {
if (this._consumeTokenIfType(TokenType.NewLine)) {
let indent = 0;
while (true) {
const nextToken = this._getNextToken();
if (nextToken.type === TokenType.Indent) {
indent++;
}
if (nextToken.type === TokenType.Dedent) {
if ((nextToken as DedentToken).isDedentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), nextToken);
}
indent--;
if (indent === 0) {
break;
}
}
if (nextToken.type === TokenType.EndOfStream) {
break;
}
}
} else {
// consume tokens
this._parseSimpleStatement();
}
if (this._tokenIndex > 0) {
extendRange(suite, this._tokenizerOutput!.tokens.getItemAt(this._tokenIndex - 1));
}
return suite;
}
if (postColonCallback) {
postColonCallback();
}
const wasFunction = this._isInFunction;
this._isInFunction = isFunction;
if (this._consumeTokenIfType(TokenType.NewLine)) {
if (postColonCallback) {
postColonCallback();
}
const possibleIndent = this._peekToken();
if (!this._consumeTokenIfType(TokenType.Indent)) {
this._addSyntaxError(LocMessage.expectedIndentedBlock(), this._peekToken());
return suite;
}
const bodyIndentToken = possibleIndent as IndentToken;
if (bodyIndentToken.isIndentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), bodyIndentToken);
}
while (true) {
// Handle a common error here and see if we can recover.
const nextToken = this._peekToken();
if (nextToken.type === TokenType.Indent) {
this._getNextToken();
const indentToken = nextToken as IndentToken;
if (indentToken.isIndentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), indentToken);
} else {
this._addSyntaxError(LocMessage.unexpectedIndent(), nextToken);
}
} else if (nextToken.type === TokenType.Dedent) {
// When we see a dedent, stop before parsing the dedented statement.
const dedentToken = nextToken as DedentToken;
if (!dedentToken.matchesIndent) {
this._addSyntaxError(LocMessage.inconsistentIndent(), dedentToken);
}
if (dedentToken.isDedentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), dedentToken);
}
// When the suite is incomplete (no statements), leave the dedent token for
// recovery. This allows a single dedent token to cause us to break out of
// multiple levels of nested suites. Also extend the suite's range in this
// case so it is multi-line as this works better with indentationUtils.
if (suite.d.statements.length > 0) {
this._consumeTokenIfType(TokenType.Dedent);
} else {
extendRange(suite, dedentToken);
}
// Did this dedent take us to an indent amount that is less than the
// initial indent of the suite body?
if (!bodyIndentToken || dedentToken.indentAmount < bodyIndentToken.indentAmount) {
break;
} else if (dedentToken.indentAmount === bodyIndentToken.indentAmount) {
// If the next token is also a dedent that reduces the indent
// level to a less than the initial indent of the suite body, swallow
// the extra dedent to help recover the parse.
const nextToken = this._peekToken();
if (this._consumeTokenIfType(TokenType.Dedent)) {
extendRange(suite, nextToken);
break;
}
}
}
const statement = this._parseStatement();
if (!statement) {
// Perform basic error recovery to get to the next line.
this._consumeTokensUntilType([TokenType.NewLine]);
} else {
statement.parent = suite;
suite.d.statements.push(statement);
}
if (this._peekTokenType() === TokenType.EndOfStream) {
break;
}
}
} else {
const simpleStatement = this._parseSimpleStatement();
suite.d.statements.push(simpleStatement);
simpleStatement.parent = suite;
// Viper 扩展:`enum X: a, b, c` 允许换行 + TAB 继续写成员(等效缩进块)。
// 注:_parseSimpleStatement 已吃掉行尾的 NewLine,这里只需看 Indent。
if (allowTrailingIndent && this._peekTokenType() === TokenType.Indent) {
const bodyIndentToken = this._getNextToken() as IndentToken;
if (bodyIndentToken.isIndentAmbiguous) {
this._addSyntaxError(LocMessage.inconsistentTabs(), bodyIndentToken);
}
while (true) {
const nextToken = this._peekToken();
if (nextToken.type === TokenType.Dedent || nextToken.type === TokenType.EndOfStream) {
if (nextToken.type === TokenType.Dedent) {
this._getNextToken();
}
break;
}
const statement = this._parseStatement();
if (!statement) {
// Perform basic error recovery to get to the next line.
this._consumeTokensUntilType([TokenType.NewLine]);
} else {
statement.parent = suite;
suite.d.statements.push(statement);
}
}
}
}
if (suite.d.statements.length > 0) {
extendRange(suite, suite.d.statements[suite.d.statements.length - 1]);
}
this._isInFunction = wasFunction;
return suite;
}
// for_stmt: [async] 'for' exprlist 'in' testlist suite ['else' suite]
private _parseForStatement(asyncToken?: KeywordToken): ForNode {
const forToken = this._getKeywordToken(KeywordType.For);
const targetExpr = this._parseExpressionListAsPossibleTuple(
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedExpr(),
forToken
);
let seqExpr: ExpressionNode;
let forSuite: SuiteNode;
let elseSuite: SuiteNode | undefined;
if (!this._consumeTokenIfKeyword(KeywordType.In)) {
seqExpr = this._handleExpressionParseError(ErrorExpressionCategory.MissingIn, LocMessage.expectedIn());
forSuite = SuiteNode.create(this._peekToken());
} else {
seqExpr = this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ false,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedInExpr()
);
forSuite = this._parseLoopSuite();
// Versions of Python earlier than 3.9 didn't allow unpack operators if the
// tuple wasn't enclosed in parentheses.
if (
PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_9) &&
!this._parseOptions.isStubFile
) {
if (seqExpr.nodeType === ParseNodeType.Tuple && !seqExpr.d.hasParens) {
let sawStar = false;
seqExpr.d.items.forEach((expr) => {
if (expr.nodeType === ParseNodeType.Unpack && !sawStar) {
this._addSyntaxError(LocMessage.unpackOperatorNotAllowed(), expr);
sawStar = true;
}
});
}
}
if (this._consumeTokenIfKeyword(KeywordType.Else)) {
elseSuite = this._parseSuite(this._isInFunction);
}
}
const forNode = ForNode.create(forToken, targetExpr, seqExpr, forSuite);
forNode.d.elseSuite = elseSuite;
if (elseSuite) {
extendRange(forNode, elseSuite);
elseSuite.parent = forNode;
}
if (asyncToken) {
forNode.d.isAsync = true;
forNode.d.asyncToken = asyncToken;
extendRange(forNode, asyncToken);
}
if (forSuite.d.typeComment) {
forNode.d.typeComment = forSuite.d.typeComment;
}
return forNode;
}
// comp_iter: comp_for | comp_if
private _tryParseComprehension(target: ParseNode, isGenerator: boolean): ComprehensionNode | undefined {
const compFor = this._tryParseCompForStatement();
if (!compFor) {
return undefined;
}
if (target.nodeType === ParseNodeType.Unpack) {
this._addSyntaxError(LocMessage.unpackIllegalInComprehension(), target);
} else if (target.nodeType === ParseNodeType.DictionaryExpandEntry) {
this._addSyntaxError(LocMessage.dictExpandIllegalInComprehension(), target);
}
const compNode = ComprehensionNode.create(target, isGenerator);
const forIfList: ComprehensionForIfNode[] = [compFor];
while (true) {
const compIter = this._tryParseCompForStatement() || this._tryParseCompIfStatement();
if (!compIter) {
break;
}
compIter.parent = compNode;
forIfList.push(compIter);
}
compNode.d.forIfNodes = forIfList;
if (forIfList.length > 0) {
forIfList.forEach((comp) => {
comp.parent = compNode;
});
extendRange(compNode, forIfList[forIfList.length - 1]);
}
return compNode;
}
// comp_for: ['async'] 'for' exprlist 'in' or_test [comp_iter]
private _tryParseCompForStatement(): ComprehensionForNode | undefined {
const startTokenKeywordType = this._peekKeywordType();
if (startTokenKeywordType === KeywordType.Async) {
const nextToken = this._peekToken(1) as KeywordToken;
if (nextToken.type !== TokenType.Keyword || nextToken.keywordType !== KeywordType.For) {
return undefined;
}
} else if (startTokenKeywordType !== KeywordType.For) {
return undefined;
}
let asyncToken: KeywordToken | undefined;
if (this._peekKeywordType() === KeywordType.Async) {
asyncToken = this._getKeywordToken(KeywordType.Async);
}
const forToken = this._getKeywordToken(KeywordType.For);
const targetExpr = this._parseExpressionListAsPossibleTuple(
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedExpr(),
forToken
);
let seqExpr: ExpressionNode | undefined;
if (!this._consumeTokenIfKeyword(KeywordType.In)) {
seqExpr = this._handleExpressionParseError(ErrorExpressionCategory.MissingIn, LocMessage.expectedIn());
} else {
this._disallowAssignmentExpression(() => {
seqExpr = this._parseOrTest();
});
}
const compForNode = ComprehensionForNode.create(asyncToken || forToken, targetExpr, seqExpr!);
if (asyncToken) {
compForNode.d.isAsync = true;
compForNode.d.asyncToken = asyncToken;
}
return compForNode;
}
// comp_if: 'if' test_nocond [comp_iter]
// comp_iter: comp_for | comp_if
private _tryParseCompIfStatement(): ComprehensionIfNode | undefined {
if (this._peekKeywordType() !== KeywordType.If) {
return undefined;
}
const ifToken = this._getKeywordToken(KeywordType.If);
const ifExpr =
this._tryParseLambdaExpression() ||
this._parseAssignmentExpression(/* disallowAssignmentExpression */ true);
const compIfNode = ComprehensionIfNode.create(ifToken, ifExpr);
return compIfNode;
}
// while_stmt: 'while' test suite ['else' suite]
private _parseWhileStatement(): WhileNode {
const whileToken = this._getKeywordToken(KeywordType.While);
const whileNode = WhileNode.create(
whileToken,
this._parseTestExpression(/* allowAssignmentExpression */ true),
this._parseLoopSuite()
);
if (this._consumeTokenIfKeyword(KeywordType.Else)) {
whileNode.d.elseSuite = this._parseSuite(this._isInFunction);
whileNode.d.elseSuite.parent = whileNode;
extendRange(whileNode, whileNode.d.elseSuite);
}
return whileNode;
}
// try_stmt: ('try' suite
// ((except_clause suite)+
// ['else' suite]
// ['finally' suite] |
// 'finally' suite))
// except_clause: 'except' [test ['as' NAME]]
private _parseTryStatement(): TryNode {
const tryToken = this._getKeywordToken(KeywordType.Try);
const trySuite = this._parseSuite(this._isInFunction);
const tryNode = TryNode.create(tryToken, trySuite);
let sawCatchAllExcept = false;
let reportedExceptGroupMismatch = false;
while (true) {
const exceptToken = this._peekToken();
if (!this._consumeTokenIfKeyword(KeywordType.Except)) {
break;
}
// See if this is a Python 3.11 exception group.
const possibleStarToken = this._peekToken();
let isExceptGroup = false;
if (this._consumeTokenIfOperator(OperatorType.Multiply)) {
if (
PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_11) &&
!this._parseOptions.isStubFile
) {
this._addSyntaxError(LocMessage.exceptionGroupIncompatible(), possibleStarToken);
}
isExceptGroup = true;
if (!reportedExceptGroupMismatch && tryNode.d.exceptClauses.some((clause) => !clause.d.isExceptGroup)) {
this._addSyntaxError(LocMessage.exceptGroupMismatch(), possibleStarToken);
reportedExceptGroupMismatch = true;
}
} else {
if (!reportedExceptGroupMismatch && tryNode.d.exceptClauses.some((clause) => clause.d.isExceptGroup)) {
this._addSyntaxError(LocMessage.exceptGroupMismatch(), possibleStarToken);
reportedExceptGroupMismatch = true;
}
}
let typeExpr: ExpressionNode | undefined;
let symbolName: IdentifierToken | undefined;
let isAsKeywordAllowed = true;
if (this._peekTokenType() !== TokenType.Colon) {
const listResult = this._parseExpressionListGeneric(() =>
this._parseTestExpression(/* allowAssignmentExpression */ true)
);
if (listResult.parseError) {
typeExpr = listResult.parseError;
} else {
typeExpr = this._makeExpressionOrTuple(listResult, /* enclosedInParens */ false);
// Python 3.14 allows more than one exception type to be provided in
// an except clause.
if (listResult.list.length > 1) {
if (PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_14)) {
this._addSyntaxError(LocMessage.exceptRequiresParens(), typeExpr);
}
isAsKeywordAllowed = false;
}
}
if (this._consumeTokenIfKeyword(KeywordType.As)) {
if (!isAsKeywordAllowed) {
this._addSyntaxError(LocMessage.exceptWithAsRequiresParens(), typeExpr);
}
symbolName = this._getTokenIfIdentifier();
if (!symbolName) {
this._addSyntaxError(LocMessage.expectedNameAfterAs(), this._peekToken());
}
}
} else if (isExceptGroup) {
this._addSyntaxError(LocMessage.exceptGroupRequiresType(), this._peekToken());
}
if (!typeExpr) {
if (sawCatchAllExcept) {
this._addSyntaxError(LocMessage.duplicateCatchAll(), exceptToken);
}
sawCatchAllExcept = true;
} else {
if (sawCatchAllExcept) {
this._addSyntaxError(LocMessage.namedExceptAfterCatchAll(), typeExpr);
}
}
const exceptSuite = this._parseExceptSuite(isExceptGroup, () => this._parseSuite(this._isInFunction));
const exceptNode = ExceptNode.create(exceptToken, exceptSuite, isExceptGroup);
if (typeExpr) {
exceptNode.d.typeExpr = typeExpr;
exceptNode.d.typeExpr.parent = exceptNode;
}
if (symbolName) {
exceptNode.d.name = NameNode.create(symbolName);
exceptNode.d.name.parent = exceptNode;
}
tryNode.d.exceptClauses.push(exceptNode);
exceptNode.parent = tryNode;
}
if (tryNode.d.exceptClauses.length > 0) {
extendRange(tryNode, tryNode.d.exceptClauses[tryNode.d.exceptClauses.length - 1]);
if (this._consumeTokenIfKeyword(KeywordType.Else)) {
tryNode.d.elseSuite = this._parseSuite(this._isInFunction);
tryNode.d.elseSuite.parent = tryNode;
extendRange(tryNode, tryNode.d.elseSuite);
}
}
if (this._consumeTokenIfKeyword(KeywordType.Finally)) {
const wasInFinallyBlock = this._isInFinallyBlock;
const wasInFinallyLoop = this._isInFinallyLoop;
this._isInFinallyBlock = true;
this._isInFinallyLoop = this._isInLoop;
tryNode.d.finallySuite = this._parseSuite(this._isInFunction);
this._isInFinallyBlock = wasInFinallyBlock;
this._isInFinallyLoop = wasInFinallyLoop;
tryNode.d.finallySuite.parent = tryNode;
extendRange(tryNode, tryNode.d.finallySuite);
}
if (!tryNode.d.finallySuite && tryNode.d.exceptClauses.length === 0) {
this._addSyntaxError(LocMessage.tryWithoutExcept(), tryToken);
}
return tryNode;
}
// funcdef: 'def' NAME parameters ['->' test] ':' suite
// parameters: '(' [typedargslist] ')'
private _parseFunctionDef(
asyncToken?: KeywordToken,
decorators?: DecoratorNode[],
viperModifier?: string,
defTokenOverride?: Token
): FunctionNode | ErrorNode {
// Viper 扩展:extern/export 直接声明形式(`extern A(...)`)没有 def 关键字,
// 用修饰关键字 token 作为范围起点;否则正常消费 Def 关键字 token。
// 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) {
this._addSyntaxError(LocMessage.expectedFunctionName(), defToken);
return ErrorNode.create(
defToken,
ErrorExpressionCategory.MissingFunctionParameterList,
undefined,
decorators
);
}
let typeParameters: TypeParameterListNode | undefined;
const possibleOpenBracket = this._peekToken();
if (possibleOpenBracket.type === TokenType.OpenBracket) {
typeParameters = this._parseTypeParameterList();
if (
!this._parseOptions.isStubFile &&
PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_12)
) {
this._addSyntaxError(LocMessage.functionTypeParametersIllegal(), typeParameters);
}
}
const openParenToken = this._peekToken();
if (!this._consumeTokenIfType(TokenType.OpenParenthesis)) {
this._addSyntaxError(LocMessage.expectedOpenParen(), this._peekToken());
return ErrorNode.create(
nameToken,
ErrorExpressionCategory.MissingFunctionParameterList,
NameNode.create(nameToken),
decorators
);
}
const paramList = this._parseVarArgsList(TokenType.CloseParenthesis, /* allowAnnotations */ true);
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
this._addSyntaxError(LocMessage.expectedCloseParen(), openParenToken);
this._consumeTokensUntilType([TokenType.Colon]);
}
let returnType: ExpressionNode | undefined;
if (this._consumeTokenIfType(TokenType.Arrow)) {
returnType = this._parseTypeAnnotation();
}
let functionTypeAnnotationToken: StringToken | undefined;
const wasInExceptionGroup = this._isInExceptionGroup;
this._isInExceptionGroup = false;
const wasInFinallyBlock = this._isInFinallyBlock;
const wasInFinallyLoop = this._isInFinallyLoop;
this._isInFinallyBlock = false;
this._isInFinallyLoop = false;
let suite: SuiteNode;
const possibleSuiteStart = this._peekToken();
if (
(viperModifier === 'extern' || viperModifier === 'export') &&
possibleSuiteStart.type !== TokenType.Colon
) {
// Viper extern/export 声明允许省略函数体(`extern def f(...) -> T` 无冒号、无 pass)。
// 创建空 suite 并消费行尾换行;`: pass` 方式仍走 _parseSuite 正常解析。
suite = SuiteNode.create(possibleSuiteStart);
if (this._consumeTokenIfType(TokenType.NewLine)) {
extendRange(suite, this._tokenizerOutput!.tokens.getItemAt(this._tokenIndex - 1));
}
} else {
// Viper 扩展:函数体内 `virtual def` / `novirtual def` 不是类成员,
// 解析函数体期间清空所处的类形态,使嵌套 def 的位置校验按"不在类内"处理。
const wasEnclosingClassKind = this._viperEnclosingClassKind;
this._viperEnclosingClassKind = undefined;
suite = this._parseSuite(/* isFunction */ true, this._parseOptions.skipFunctionAndClassBody, () => {
if (!functionTypeAnnotationToken) {
functionTypeAnnotationToken = this._getTypeAnnotationCommentText();
}
});
this._viperEnclosingClassKind = wasEnclosingClassKind;
}
this._isInExceptionGroup = wasInExceptionGroup;
this._isInFinallyBlock = wasInFinallyBlock;
this._isInFinallyLoop = wasInFinallyLoop;
const functionNode = FunctionNode.create(defToken, NameNode.create(nameToken), suite, typeParameters, viperModifier, isDefine);
functionNode.d.viperModifier = viperModifier;
if (asyncToken) {
functionNode.d.isAsync = true;
extendRange(functionNode, asyncToken);
}
functionNode.d.params = paramList;
paramList.forEach((param) => {
param.parent = functionNode;
});
if (decorators) {
functionNode.d.decorators = decorators;
decorators.forEach((decorator) => {
decorator.parent = functionNode;
});
if (decorators.length > 0) {
extendRange(functionNode, decorators[0]);
}
}
if (returnType) {
functionNode.d.returnAnnotation = returnType;
functionNode.d.returnAnnotation.parent = functionNode;
extendRange(functionNode, returnType);
}
// If there was a type annotation comment for the function,
// parse it now.
if (functionTypeAnnotationToken) {
this._parseFunctionTypeAnnotationComment(functionTypeAnnotationToken, functionNode);
}
return functionNode;
}
// typedargslist: (
// tfpdef ['=' test] (',' tfpdef ['=' test])*
// [ ','
// [
// '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
// | '**' tfpdef [',']
// ]
// ]
// | '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
// | '**' tfpdef [','])
// tfpdef: NAME [':' test]
// vfpdef: NAME;
private _parseVarArgsList(terminator: TokenType, allowAnnotations: boolean): ParameterNode[] {
const paramMap = new Map<string, string>();
const paramList: ParameterNode[] = [];
let sawDefaultParam = false;
let reportedNonDefaultParamErr = false;
let sawKeywordOnlySeparator = false;
let sawPositionOnlySeparator = false;
let sawKeywordOnlyParamAfterSeparator = false;
let sawArgs = false;
let sawKwArgs = false;
while (true) {
if (this._peekTokenType() === terminator) {
break;
}
const param = this._parseParameter(allowAnnotations);
if (!param) {
this._consumeTokensUntilType([terminator]);
break;
}
if (param.d.name) {
const name = param.d.name.d.value;
if (paramMap.has(name)) {
this._addSyntaxError(LocMessage.duplicateParam().format({ name }), param.d.name);
} else {
paramMap.set(name, name);
}
} else if (param.d.category === ParamCategory.Simple) {
if (paramList.length === 0) {
this._addSyntaxError(LocMessage.positionOnlyFirstParam(), param);
}
}
if (param.d.category === ParamCategory.Simple) {
if (!param.d.name) {
if (sawPositionOnlySeparator) {
this._addSyntaxError(LocMessage.duplicatePositionOnly(), param);
} else if (sawKeywordOnlySeparator) {
this._addSyntaxError(LocMessage.positionOnlyAfterKeywordOnly(), param);
} else if (sawArgs) {
this._addSyntaxError(LocMessage.positionOnlyAfterArgs(), param);
}
sawPositionOnlySeparator = true;
} else {
if (sawKeywordOnlySeparator) {
sawKeywordOnlyParamAfterSeparator = true;
}
if (param.d.defaultValue) {
sawDefaultParam = true;
} else if (sawDefaultParam && !sawKeywordOnlySeparator && !sawArgs) {
// Report this error only once.
if (!reportedNonDefaultParamErr) {
this._addSyntaxError(LocMessage.nonDefaultAfterDefault(), param);
reportedNonDefaultParamErr = true;
}
}
}
}
paramList.push(param);
if (param.d.category === ParamCategory.ArgsList) {
if (!param.d.name) {
if (sawKeywordOnlySeparator) {
this._addSyntaxError(LocMessage.duplicateKeywordOnly(), param);
} else if (sawArgs) {
this._addSyntaxError(LocMessage.keywordOnlyAfterArgs(), param);
}
sawKeywordOnlySeparator = true;
} else {
if (sawKeywordOnlySeparator || sawArgs) {
this._addSyntaxError(LocMessage.duplicateArgsParam(), param);
}
sawArgs = true;
}
}
if (param.d.category === ParamCategory.KwargsDict) {
if (sawKwArgs) {
this._addSyntaxError(LocMessage.duplicateKwargsParam(), param);
}
sawKwArgs = true;
// A **kwargs cannot immediately follow a keyword-only separator ("*").
if (sawKeywordOnlySeparator && !sawKeywordOnlyParamAfterSeparator) {
this._addSyntaxError(LocMessage.keywordParameterMissing(), param);
}
} else if (sawKwArgs) {
this._addSyntaxError(LocMessage.paramAfterKwargsParam(), param);
}
const foundComma = this._consumeTokenIfType(TokenType.Comma);
if (allowAnnotations && !param.d.annotation) {
// Look for a type annotation comment at the end of the line.
const typeAnnotationComment = this._parseVariableTypeAnnotationComment();
if (typeAnnotationComment) {
param.d.annotationComment = typeAnnotationComment;
param.d.annotationComment.parent = param;
extendRange(param, param.d.annotationComment);
}
}
if (!foundComma) {
break;
}
}
if (paramList.length > 0) {
const lastParam = paramList[paramList.length - 1];
if (lastParam.d.category === ParamCategory.ArgsList && !lastParam.d.name) {
this._addSyntaxError(LocMessage.expectedNamedParameter(), lastParam);
}
}
return paramList;
}
private _parseParameter(allowAnnotations: boolean): ParameterNode {
let starCount = 0;
let slashCount = 0;
const firstToken = this._peekToken();
if (this._consumeTokenIfOperator(OperatorType.Multiply)) {
starCount = 1;
} else if (this._consumeTokenIfOperator(OperatorType.Power)) {
starCount = 2;
} else if (this._consumeTokenIfOperator(OperatorType.Divide)) {
if (
PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_8) &&
!this._parseOptions.isStubFile
) {
this._addSyntaxError(LocMessage.positionOnlyIncompatible(), firstToken);
}
slashCount = 1;
}
const paramName = this._getTokenIfIdentifier();
if (!paramName) {
if (starCount === 1) {
const paramNode = ParameterNode.create(firstToken, ParamCategory.ArgsList);
return paramNode;
} else if (slashCount === 1) {
const paramNode = ParameterNode.create(firstToken, ParamCategory.Simple);
return paramNode;
}
// Check for the Python 2.x parameter sublist syntax and handle it gracefully.
if (this._peekTokenType() === TokenType.OpenParenthesis) {
const sublistStart = this._getNextToken();
if (this._consumeTokensUntilType([TokenType.CloseParenthesis])) {
this._getNextToken();
}
this._addSyntaxError(LocMessage.sublistParamsIncompatible(), sublistStart);
} else {
this._addSyntaxError(LocMessage.expectedParamName(), this._peekToken());
}
}
let paramType = ParamCategory.Simple;
if (starCount === 1) {
paramType = ParamCategory.ArgsList;
} else if (starCount === 2) {
paramType = ParamCategory.KwargsDict;
}
const paramNode = ParameterNode.create(firstToken, paramType);
if (paramName) {
paramNode.d.name = NameNode.create(paramName);
paramNode.d.name.parent = paramNode;
extendRange(paramNode, paramName);
}
if (allowAnnotations && this._consumeTokenIfType(TokenType.Colon)) {
paramNode.d.annotation = this._parseTypeAnnotation(paramType === ParamCategory.ArgsList);
paramNode.d.annotation.parent = paramNode;
extendRange(paramNode, paramNode.d.annotation);
}
if (this._consumeTokenIfOperator(OperatorType.Assign)) {
paramNode.d.defaultValue = this._parseTestExpression(/* allowAssignmentExpression */ false);
paramNode.d.defaultValue.parent = paramNode;
extendRange(paramNode, paramNode.d.defaultValue);
if (starCount > 0) {
this._addSyntaxError(LocMessage.defaultValueNotAllowed(), paramNode.d.defaultValue);
}
}
return paramNode;
}
// with_stmt: 'with' with_item (',' with_item)* ':' suite
// Python 3.10 adds support for optional parentheses around
// with_item list.
private _parseWithStatement(asyncToken?: KeywordToken): WithNode {
const withToken = this._getKeywordToken(KeywordType.With);
let withItemList: WithItemNode[] = [];
const possibleParen = this._peekToken();
// If the expression starts with a paren, parse it as though the
// paren is enclosing the list of "with items". This is done as a
// "dry run" to determine whether the entire list of "with items"
// is enclosed in parentheses.
let isParenthesizedWithItemList = false;
let isParenthesizedDisallowed = false;
if (possibleParen.type === TokenType.OpenParenthesis) {
const openParenTokenIndex = this._tokenIndex;
this._suppressErrors(() => {
this._getNextToken();
while (true) {
withItemList.push(this._parseWithItem());
if (!this._consumeTokenIfType(TokenType.Comma)) {
break;
}
if (this._peekToken().type === TokenType.CloseParenthesis) {
break;
}
}
if (
this._peekToken().type === TokenType.CloseParenthesis &&
this._peekToken(1).type === TokenType.Colon
) {
isParenthesizedWithItemList = true;
// Some forms of parenthesized context with statements were not
// allowed prior to Python 3.9. Is this such a form?
isParenthesizedDisallowed = withItemList.length !== 1 || withItemList[0].d.target !== undefined;
}
this._tokenIndex = openParenTokenIndex;
withItemList = [];
});
}
if (isParenthesizedWithItemList) {
this._consumeTokenIfType(TokenType.OpenParenthesis);
if (isParenthesizedDisallowed && PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_9)) {
this._addSyntaxError(LocMessage.parenthesizedContextManagerIllegal(), possibleParen);
}
}
while (true) {
withItemList.push(this._parseWithItem());
if (!this._consumeTokenIfType(TokenType.Comma)) {
break;
}
if (this._peekToken().type === TokenType.CloseParenthesis) {
break;
}
}
if (isParenthesizedWithItemList) {
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
this._addSyntaxError(LocMessage.expectedCloseParen(), possibleParen);
}
}
let typeComment: StringToken | undefined;
const withSuite = this._parseSuite(this._isInFunction, /* skipBody */ false, () => {
const comment = this._getTypeAnnotationCommentText();
if (comment) {
typeComment = comment;
}
});
const withNode = WithNode.create(withToken, withSuite);
if (asyncToken) {
withNode.d.isAsync = true;
withNode.d.asyncToken = asyncToken;
extendRange(withNode, asyncToken);
}
if (typeComment) {
withNode.d.typeComment = typeComment;
}
withNode.d.withItems = withItemList;
withItemList.forEach((withItem) => {
withItem.parent = withNode;
});
return withNode;
}
// with_item: test ['as' expr]
private _parseWithItem(): WithItemNode {
const expr = this._parseTestExpression(/* allowAssignmentExpression */ true);
const itemNode = WithItemNode.create(expr);
if (this._consumeTokenIfKeyword(KeywordType.As)) {
itemNode.d.target = this._parseExpression(/* allowUnpack */ false);
itemNode.d.target.parent = itemNode;
extendRange(itemNode, itemNode.d.target);
}
return itemNode;
}
// decorators: decorator+
// decorated: decorators (classdef | funcdef | async_funcdef)
private _parseDecorated(): StatementNode | undefined {
const decoratorList: DecoratorNode[] = [];
while (true) {
if (this._peekOperatorType() === OperatorType.MatrixMultiply) {
decoratorList.push(this._parseDecorator());
} else {
break;
}
}
const nextToken = this._peekToken() as KeywordToken;
if (nextToken.type === TokenType.Keyword) {
if (nextToken.keywordType === KeywordType.Async) {
this._getNextToken();
if (this._peekKeywordType() !== KeywordType.Def) {
this._addSyntaxError(LocMessage.expectedFunctionAfterAsync(), this._peekToken());
} else {
return this._parseFunctionDef(nextToken, decoratorList);
}
} 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);
} else if (nextToken.keywordType === KeywordType.ViperModifier) {
// Viper 扩展:装饰器之后的 vtable 修饰(如 `@Foo` + `virtual class X`
// / `virtual def f(...)`)。与 _parseStatement 的同名分支保持一致。
const modifierToken = this._peekToken();
const modifierText = this._fileContents!.slice(
modifierToken.start,
modifierToken.start + modifierToken.length
);
if (modifierText === 'virtual' || modifierText === 'novirtual') {
const afterModifierKeywordType =
this._peekToken(1).type === TokenType.Keyword
? (this._peekToken(1) as KeywordToken).keywordType
: undefined;
if (afterModifierKeywordType === KeywordType.Class) {
this._getNextToken();
return this._parseClassDef(decoratorList, modifierText);
}
if (afterModifierKeywordType === KeywordType.Def) {
this._validateViperVirtualMethodPlacement(modifierText, modifierToken);
this._getNextToken();
return this._parseFunctionDef(undefined, decoratorList, modifierText);
}
this._addSyntaxError(
LocMessage.viperVirtualModifierRequiresClassOrDef().format({ name: modifierText }),
modifierToken
);
}
// 其他修饰关键字(inline/export/extern)不在装饰器后支持,
// 保持原行为:落到下方 expectedAfterDecorator 报错。
}
}
this._addSyntaxError(LocMessage.expectedAfterDecorator(), this._peekToken());
// Return a dummy class declaration so the completion provider has
// some parse nodes to work with.
return ClassNode.createDummyForDecorators(decoratorList);
}
// decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
private _parseDecorator(): DecoratorNode {
const atOperator = this._getNextToken() as OperatorToken;
assert(atOperator.operatorType === OperatorType.MatrixMultiply);
const expression = this._parseTestExpression(/* allowAssignmentExpression */ true);
// Versions of Python prior to 3.9 support a limited set of
// expression forms.
if (PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_9)) {
let isSupportedExpressionForm = false;
if (this._isNameOrMemberAccessExpression(expression)) {
isSupportedExpressionForm = true;
} else if (
expression.nodeType === ParseNodeType.Call &&
this._isNameOrMemberAccessExpression(expression.d.leftExpr)
) {
isSupportedExpressionForm = true;
}
if (!isSupportedExpressionForm) {
this._addSyntaxError(LocMessage.expectedDecoratorExpr(), expression);
}
}
// Viper 扩展:`@__attribute__(section(".x"), always_inline)` desugar 为
// `@c.Attribute(t.attr.section(".x"), t.attr.always_inline)`
// ——与 TPC/TPV 的 includes/ast/parser.vp `_DesugarMagicAttribute` 同形,
// class 与 def 共用(装饰器链统一走本函数);其它装饰器原样返回。
const decoratorNode = DecoratorNode.create(
atOperator,
this._desugarViperMagicAttribute(expression)
);
if (!this._consumeTokenIfType(TokenType.NewLine)) {
this._addSyntaxError(LocMessage.expectedDecoratorNewline(), this._peekToken());
this._consumeTokensUntilType([TokenType.NewLine]);
}
return decoratorNode;
}
private _isNameOrMemberAccessExpression(expression: ExpressionNode): boolean {
if (expression.nodeType === ParseNodeType.Name) {
return true;
} else if (expression.nodeType === ParseNodeType.MemberAccess) {
return this._isNameOrMemberAccessExpression(expression.d.leftExpr);
}
return false;
}
// Viper 扩展:判断 `define` 之后是否为编译期常量声明(`NAME: 类型 = 值` / `NAME = 值`)。
// 位置基于 define 关键字:`_peekToken(1)` 是名字,`_peekToken(2)` 是 ':' 或 '='。
private _isViperDefineConstDeclaration(): boolean {
if (this._peekToken(1).type !== TokenType.Identifier) {
return false;
}
const afterName = this._peekToken(2);
if (afterName.type === TokenType.Colon) {
return true;
}
return afterName.type === TokenType.Operator && (afterName as OperatorToken).operatorType === OperatorType.Assign;
}
// Viper 扩展:校验 vtable 修饰方法(`virtual def` / `novirtual def`)的出现位置。
// - 必须位于类体内(模块级、函数体内均非法);
// - `virtual def` 不能位于 `novirtual class` 内(该形态的类不生成 vtable)。
private _validateViperVirtualMethodPlacement(modifierText: string, modifierToken: Token): void {
const enclosingClassKind = this._viperEnclosingClassKind;
if (!enclosingClassKind) {
this._addSyntaxError(
modifierText === 'virtual'
? LocMessage.viperVirtualDefPlacement()
: LocMessage.viperNoVirtualDefPlacement(),
modifierToken
);
return;
}
if (modifierText === 'virtual' && enclosingClassKind === 'novirtual') {
this._addSyntaxError(LocMessage.viperVirtualDefInNoVirtualClass(), modifierToken);
}
}
// classdef: 'class' NAME ['(' [arglist] ')'] suite
private _parseClassDef(decorators?: DecoratorNode[], viperClassModifier?: string): ClassNode {
const classToken = this._getKeywordToken(KeywordType.Class);
let nameToken = this._getTokenIfIdentifier();
if (!nameToken) {
this._addSyntaxError(LocMessage.expectedClassName(), this._peekToken());
nameToken = IdentifierToken.create(0, 0, '', /* comments */ undefined);
}
let typeParameters: TypeParameterListNode | undefined;
const possibleOpenBracket = this._peekToken();
if (possibleOpenBracket.type === TokenType.OpenBracket) {
typeParameters = this._parseTypeParameterList();
if (
!this._parseOptions.isStubFile &&
PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_12)
) {
this._addSyntaxError(LocMessage.classTypeParametersIllegal(), typeParameters);
}
}
let argList: ArgumentNode[] = [];
const openParenToken = this._peekToken();
if (this._consumeTokenIfType(TokenType.OpenParenthesis)) {
argList = this._parseArgList().args;
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
this._addSyntaxError(LocMessage.expectedCloseParen(), openParenToken);
}
}
// Viper 扩展:类体内的 vtable 修饰形态(供 `virtual def` / `novirtual def` 位置校验)。
const enclosingClassKind: 'plain' | 'virtual' | 'novirtual' =
viperClassModifier === 'virtual' ? 'virtual' : viperClassModifier === 'novirtual' ? 'novirtual' : 'plain';
const wasEnclosingClassKind = this._viperEnclosingClassKind;
this._viperEnclosingClassKind = enclosingClassKind;
const suite = this._parseSuite(
/* isFunction */ false,
this._parseOptions.skipFunctionAndClassBody,
/* postColonCallback */ undefined,
/* allowTrailingIndent */ classToken.isViperEnum === true
);
this._viperEnclosingClassKind = wasEnclosingClassKind;
const classNode = ClassNode.create(classToken, NameNode.create(nameToken), suite, typeParameters);
classNode.d.arguments = argList;
argList.forEach((arg) => {
arg.parent = classNode;
});
// Viper 扩展:`enum X:` 语法声明的枚举(tokenizer 在 class 关键字 token 上打标记)。
if (classToken.isViperEnum) {
classNode.d.isViperEnum = true;
}
// Viper 扩展:`virtual class X` / `novirtual class X` 的修饰关键字。
if (viperClassModifier) {
classNode.d.viperClassModifier = viperClassModifier;
}
if (decorators) {
classNode.d.decorators = decorators;
if (decorators.length > 0) {
decorators.forEach((decorator) => {
decorator.parent = classNode;
});
extendRange(classNode, decorators[0]);
}
}
return classNode;
}
private _parsePassStatement(): PassNode {
return PassNode.create(this._getKeywordToken(KeywordType.Pass));
}
private _parseBreakStatement(): BreakNode {
const breakToken = this._getKeywordToken(KeywordType.Break);
if (!this._isInLoop) {
this._addSyntaxError(LocMessage.breakOutsideLoop(), breakToken);
} else if (this._isInExceptionGroup) {
this._addSyntaxError(LocMessage.breakInExceptionGroup(), breakToken);
}
if (this._isInFinallyLoop && PythonVersion.isGreaterOrEqualTo(this._getLanguageVersion(), pythonVersion3_14)) {
this._addSyntaxError(LocMessage.finallyBreak(), breakToken);
}
return BreakNode.create(breakToken);
}
private _parseContinueStatement(): ContinueNode {
const continueToken = this._getKeywordToken(KeywordType.Continue);
if (!this._isInLoop) {
this._addSyntaxError(LocMessage.continueOutsideLoop(), continueToken);
} else if (this._isInExceptionGroup) {
this._addSyntaxError(LocMessage.continueInExceptionGroup(), continueToken);
}
if (this._isInFinallyLoop && PythonVersion.isGreaterOrEqualTo(this._getLanguageVersion(), pythonVersion3_14)) {
this._addSyntaxError(LocMessage.finallyContinue(), continueToken);
}
return ContinueNode.create(continueToken);
}
// return_stmt: 'return' [testlist]
private _parseReturnStatement(): ReturnNode {
const returnToken = this._getKeywordToken(KeywordType.Return);
const returnNode = ReturnNode.create(returnToken);
if (!this._isInFunction) {
this._addSyntaxError(LocMessage.returnOutsideFunction(), returnToken);
} else if (this._isInExceptionGroup) {
this._addSyntaxError(LocMessage.returnInExceptionGroup(), returnToken);
}
if (this._isInFinallyBlock && PythonVersion.isGreaterOrEqualTo(this._getLanguageVersion(), pythonVersion3_14)) {
this._addSyntaxError(LocMessage.finallyReturn(), returnToken);
}
if (!this._isNextTokenNeverExpression()) {
const returnExpr = this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ true,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedReturnExpr()
);
this._reportConditionalErrorForStarTupleElement(returnExpr);
returnNode.d.expr = returnExpr;
returnNode.d.expr.parent = returnNode;
extendRange(returnNode, returnExpr);
}
return returnNode;
}
// import_from: ('from' (('.' | '...')* dotted_name | ('.' | '...')+)
// 'import' ('*' | '(' import_as_names ')' | import_as_names))
// import_as_names: import_as_name (',' import_as_name)* [',']
// import_as_name: NAME ['as' NAME]
private _parseFromStatement(): ImportFromNode {
const fromToken = this._getKeywordToken(KeywordType.From);
const modName = this._parseDottedModuleName(/* allowJustDots */ true);
const importFromNode = ImportFromNode.create(fromToken, modName);
// Handle imports from __future__ specially because they can
// change the way we interpret the rest of the file.
const isFutureImport =
modName.d.leadingDots === 0 &&
modName.d.nameParts.length === 1 &&
modName.d.nameParts[0].d.value === '__future__';
const possibleInputToken = this._peekToken();
if (!this._consumeTokenIfKeyword(KeywordType.Import)) {
this._addSyntaxError(LocMessage.expectedImport(), this._peekToken());
if (!modName.d.hasTrailingDot) {
importFromNode.d.missingImport = true;
}
} else {
extendRange(importFromNode, possibleInputToken);
// Look for "*" token.
const possibleStarToken = this._peekToken();
if (this._consumeTokenIfOperator(OperatorType.Multiply)) {
extendRange(importFromNode, possibleStarToken);
importFromNode.d.isWildcardImport = true;
importFromNode.d.wildcardToken = possibleStarToken;
this._containsWildcardImport = true;
} else {
const openParenToken = this._peekToken();
const inParen = this._consumeTokenIfType(TokenType.OpenParenthesis);
let trailingCommaToken: Token | undefined;
while (true) {
const importName = this._getTokenIfIdentifier();
if (!importName) {
break;
}
trailingCommaToken = undefined;
const importFromAsNode = ImportFromAsNode.create(NameNode.create(importName));
if (this._consumeTokenIfKeyword(KeywordType.As)) {
const aliasName = this._getTokenIfIdentifier();
if (!aliasName) {
this._addSyntaxError(LocMessage.expectedImportAlias(), this._peekToken());
} else {
importFromAsNode.d.alias = NameNode.create(aliasName);
importFromAsNode.d.alias.parent = importFromAsNode;
extendRange(importFromAsNode, aliasName);
}
}
importFromNode.d.imports.push(importFromAsNode);
importFromAsNode.parent = importFromNode;
extendRange(importFromNode, importFromAsNode);
if (isFutureImport) {
// Add the future import by name.
this._futureImports.add(importName.value);
}
const nextToken = this._peekToken();
if (!this._consumeTokenIfType(TokenType.Comma)) {
break;
}
trailingCommaToken = nextToken;
}
if (importFromNode.d.imports.length === 0) {
this._addSyntaxError(LocMessage.expectedImportSymbols(), this._peekToken());
}
if (inParen) {
importFromNode.d.usesParens = true;
const nextToken = this._peekToken();
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
this._addSyntaxError(LocMessage.expectedCloseParen(), openParenToken);
} else {
extendRange(importFromNode, nextToken);
}
} else if (trailingCommaToken) {
this._addSyntaxError(LocMessage.trailingCommaInFromImport(), trailingCommaToken);
}
}
}
this._importedModules.push({
nameNode: importFromNode.d.module,
leadingDots: importFromNode.d.module.d.leadingDots,
nameParts: importFromNode.d.module.d.nameParts.map((p) => p.d.value),
importedSymbols: new Set<string>(importFromNode.d.imports.map((imp) => imp.d.name.d.value)),
});
let isTypingImport = false;
if (importFromNode.d.module.d.nameParts.length === 1) {
const firstNamePartValue = importFromNode.d.module.d.nameParts[0].d.value;
if (firstNamePartValue === 'typing' || firstNamePartValue === 'typing_extensions') {
isTypingImport = true;
}
}
if (isTypingImport) {
const typingSymbolsOfInterest = ['Literal', 'TypeAlias', 'Annotated'];
if (importFromNode.d.isWildcardImport) {
typingSymbolsOfInterest.forEach((s) => {
this._typingSymbolAliases.set(s, s);
});
} else {
importFromNode.d.imports.forEach((imp) => {
if (typingSymbolsOfInterest.some((s) => s === imp.d.name.d.value)) {
this._typingSymbolAliases.set(imp.d.alias?.d.value || imp.d.name.d.value, imp.d.name.d.value);
}
});
}
}
return importFromNode;
}
// import_name: 'import' dotted_as_names
// dotted_as_names: dotted_as_name (',' dotted_as_name)*
// dotted_as_name: dotted_name ['as' NAME]
private _parseImportStatement(): ImportNode {
const importToken = this._getKeywordToken(KeywordType.Import);
const importNode = ImportNode.create(importToken);
while (true) {
const modName = this._parseDottedModuleName();
const importAsNode = ImportAsNode.create(modName);
if (this._consumeTokenIfKeyword(KeywordType.As)) {
const aliasToken = this._getTokenIfIdentifier();
if (aliasToken) {
importAsNode.d.alias = NameNode.create(aliasToken);
importAsNode.d.alias.parent = importAsNode;
extendRange(importAsNode, importAsNode.d.alias);
} else {
this._addSyntaxError(LocMessage.expectedImportAlias(), this._peekToken());
}
}
if (importAsNode.d.module.d.leadingDots > 0) {
this._addSyntaxError(LocMessage.relativeImportNotAllowed(), importAsNode.d.module);
}
importNode.d.list.push(importAsNode);
importAsNode.parent = importNode;
const nameParts = importAsNode.d.module.d.nameParts.map((p) => p.d.value);
if (
importAsNode.d.alias ||
importAsNode.d.module.d.leadingDots > 0 ||
importAsNode.d.module.d.nameParts.length === 0
) {
this._importedModules.push({
nameNode: importAsNode.d.module,
leadingDots: importAsNode.d.module.d.leadingDots,
nameParts,
importedSymbols: undefined,
});
} else {
// Implicitly import all modules in the multi-part name if we
// are not assigning the final module to an alias.
importAsNode.d.module.d.nameParts.forEach((_, index) => {
this._importedModules.push({
nameNode: importAsNode.d.module,
leadingDots: importAsNode.d.module.d.leadingDots,
nameParts: nameParts.slice(0, index + 1),
importedSymbols: undefined,
});
});
}
if (modName.d.nameParts.length === 1) {
const firstNamePartValue = modName.d.nameParts[0].d.value;
if (firstNamePartValue === 'typing' || firstNamePartValue === 'typing_extensions') {
this._typingImportAliases.push(importAsNode.d.alias?.d.value || firstNamePartValue);
}
}
if (!this._consumeTokenIfType(TokenType.Comma)) {
break;
}
}
if (importNode.d.list.length > 0) {
extendRange(importNode, importNode.d.list[importNode.d.list.length - 1]);
}
return importNode;
}
// ('.' | '...')* dotted_name | ('.' | '...')+
// dotted_name: NAME ('.' NAME)*
private _parseDottedModuleName(allowJustDots = false): ModuleNameNode {
const moduleNameNode = ModuleNameNode.create(this._peekToken());
while (true) {
const token = this._getTokenIfType(TokenType.Ellipsis) ?? this._getTokenIfType(TokenType.Dot);
if (token) {
if (token.type === TokenType.Ellipsis) {
moduleNameNode.d.leadingDots += 3;
} else {
moduleNameNode.d.leadingDots++;
}
extendRange(moduleNameNode, token);
} else {
break;
}
}
while (true) {
const identifier = this._getTokenIfIdentifier();
if (!identifier) {
if (!allowJustDots || moduleNameNode.d.leadingDots === 0 || moduleNameNode.d.nameParts.length > 0) {
this._addSyntaxError(LocMessage.expectedModuleName(), this._peekToken());
moduleNameNode.d.hasTrailingDot = true;
}
break;
}
const namePart = NameNode.create(identifier);
moduleNameNode.d.nameParts.push(namePart);
namePart.parent = moduleNameNode;
extendRange(moduleNameNode, namePart);
const nextToken = this._peekToken();
if (!this._consumeTokenIfType(TokenType.Dot)) {
break;
}
// Extend the module name to include the dot.
extendRange(moduleNameNode, nextToken);
}
return moduleNameNode;
}
private _parseGlobalStatement(): GlobalNode {
const globalToken = this._getKeywordToken(KeywordType.Global);
const globalNode = GlobalNode.create(globalToken);
globalNode.d.targets = this._parseNameList();
if (globalNode.d.targets.length > 0) {
globalNode.d.targets.forEach((name) => {
name.parent = globalNode;
});
extendRange(globalNode, globalNode.d.targets[globalNode.d.targets.length - 1]);
}
return globalNode;
}
private _parseNonlocalStatement(): NonlocalNode {
const nonlocalToken = this._getKeywordToken(KeywordType.Nonlocal);
const nonlocalNode = NonlocalNode.create(nonlocalToken);
nonlocalNode.d.targets = this._parseNameList();
if (nonlocalNode.d.targets.length > 0) {
nonlocalNode.d.targets.forEach((name) => {
name.parent = nonlocalNode;
});
extendRange(nonlocalNode, nonlocalNode.d.targets[nonlocalNode.d.targets.length - 1]);
}
return nonlocalNode;
}
private _parseNameList(): NameNode[] {
const nameList: NameNode[] = [];
while (true) {
const name = this._getTokenIfIdentifier();
if (!name) {
this._addSyntaxError(LocMessage.expectedIdentifier(), this._peekToken());
break;
}
nameList.push(NameNode.create(name));
if (!this._consumeTokenIfType(TokenType.Comma)) {
break;
}
}
return nameList;
}
// raise_stmt: 'raise' [test ['from' test]]
// (old) raise_stmt: 'raise' [test [',' test [',' test]]]
private _parseRaiseStatement(): RaiseNode {
const raiseToken = this._getKeywordToken(KeywordType.Raise);
const raiseNode = RaiseNode.create(raiseToken);
if (!this._isNextTokenNeverExpression()) {
raiseNode.d.expr = this._parseTestExpression(/* allowAssignmentExpression */ true);
raiseNode.d.expr.parent = raiseNode;
extendRange(raiseNode, raiseNode.d.expr);
if (this._consumeTokenIfKeyword(KeywordType.From)) {
raiseNode.d.fromExpr = this._parseTestExpression(/* allowAssignmentExpression */ true);
raiseNode.d.fromExpr.parent = raiseNode;
extendRange(raiseNode, raiseNode.d.fromExpr);
}
}
return raiseNode;
}
// assert_stmt: 'assert' test [',' test]
private _parseAssertStatement(): AssertNode {
const assertToken = this._getKeywordToken(KeywordType.Assert);
const expr = this._parseTestExpression(/* allowAssignmentExpression */ false);
const assertNode = AssertNode.create(assertToken, expr);
if (this._consumeTokenIfType(TokenType.Comma)) {
const exceptionExpr = this._parseTestExpression(/* allowAssignmentExpression */ false);
assertNode.d.exceptionExpr = exceptionExpr;
assertNode.d.exceptionExpr.parent = assertNode;
extendRange(assertNode, exceptionExpr);
}
return assertNode;
}
// del_stmt: 'del' exprlist
private _parseDelStatement(): DelNode {
const delToken = this._getKeywordToken(KeywordType.Del);
const exprListResult = this._parseExpressionList(/* allowStar */ true);
if (!exprListResult.parseError && exprListResult.list.length === 0) {
this._addSyntaxError(LocMessage.expectedDelExpr(), this._peekToken());
}
const delNode = DelNode.create(delToken);
delNode.d.targets = exprListResult.list;
if (delNode.d.targets.length > 0) {
delNode.d.targets.forEach((expr) => {
expr.parent = delNode;
});
extendRange(delNode, delNode.d.targets[delNode.d.targets.length - 1]);
}
return delNode;
}
// yield_expr: 'yield' [yield_arg]
// yield_arg: 'from' test | testlist
private _parseYieldExpression(): YieldNode | YieldFromNode {
const yieldToken = this._getKeywordToken(KeywordType.Yield);
const nextToken = this._peekToken();
if (this._consumeTokenIfKeyword(KeywordType.From)) {
if (PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_3)) {
this._addSyntaxError(LocMessage.yieldFromIllegal(), nextToken);
}
return YieldFromNode.create(yieldToken, this._parseTestExpression(/* allowAssignmentExpression */ false));
}
let exprList: ExpressionNode | undefined;
if (!this._isNextTokenNeverExpression()) {
exprList = this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ false,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedYieldExpr()
);
this._reportConditionalErrorForStarTupleElement(exprList);
}
return YieldNode.create(yieldToken, exprList);
}
private _tryParseYieldExpression(): YieldNode | YieldFromNode | undefined {
if (this._peekKeywordType() !== KeywordType.Yield) {
return undefined;
}
return this._parseYieldExpression();
}
// simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
private _parseSimpleStatement(): StatementListNode {
const statement = StatementListNode.create(this._peekToken());
while (true) {
// Swallow invalid tokens to make sure we make forward progress.
if (this._peekTokenType() === TokenType.Invalid) {
const invalidToken = this._getNextToken();
const text = this._fileContents!.substr(invalidToken.start, invalidToken.length);
const firstCharCode = text.charCodeAt(0);
// If the invalid token is a line-continuation backslash at the end of the file,
// report a clearer error message consistent with Python: "Unexpected EOF".
const nextTok = this._peekToken();
const nextNextTok = this._peekToken(1);
const isBackslash = firstCharCode === Char.Backslash;
const isAtEofLineContinuation =
isBackslash && nextTok.type === TokenType.NewLine && nextNextTok.type === TokenType.EndOfStream;
if (isAtEofLineContinuation) {
this._addSyntaxError(LocMessage.unexpectedEof(), invalidToken);
} else {
// Remove any non-printable characters.
this._addSyntaxError(
LocMessage.invalidTokenChars().format({ text: `\\u${firstCharCode.toString(16)}` }),
invalidToken
);
}
this._consumeTokensUntilType([TokenType.NewLine]);
break;
}
const smallStatement = this._parseSmallStatement();
statement.d.statements.push(smallStatement);
smallStatement.parent = statement;
extendRange(statement, smallStatement);
if (smallStatement.nodeType === ParseNodeType.Error) {
// No need to log an error here. We assume that
// it was already logged by _parseSmallStatement.
break;
}
// Consume the semicolon if present.
if (!this._consumeTokenIfType(TokenType.Semicolon)) {
break;
}
const nextTokenType = this._peekTokenType();
if (nextTokenType === TokenType.NewLine || nextTokenType === TokenType.EndOfStream) {
break;
}
}
if (!this._consumeTokenIfType(TokenType.NewLine)) {
this._addSyntaxError(LocMessage.expectedNewlineOrSemicolon(), this._peekToken());
}
return statement;
}
// small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
// import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
// flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
// import_stmt: import_name | import_from
private _parseSmallStatement(): ParseNode {
// Viper 扩展:`typedef T1 = T2` 软关键字语句。
// 仅当语句起始处形如 `typedef <Name> =` 时按 typedef 解析;
// 否则 `typedef` 仍是普通标识符(`typedef = 1`、`typedef(x)` 不受影响)。
if (this._isAtViperTypedefStatement()) {
return this._parseViperTypedefStatement();
}
switch (this._peekKeywordType()) {
case KeywordType.Pass:
return this._parsePassStatement();
case KeywordType.Break:
return this._parseBreakStatement();
case KeywordType.Continue:
return this._parseContinueStatement();
case KeywordType.Return:
return this._parseReturnStatement();
case KeywordType.From:
return this._parseFromStatement();
case KeywordType.Import:
return this._parseImportStatement();
case KeywordType.Global:
return this._parseGlobalStatement();
case KeywordType.Nonlocal:
return this._parseNonlocalStatement();
case KeywordType.Raise:
return this._parseRaiseStatement();
case KeywordType.Assert:
return this._parseAssertStatement();
case KeywordType.Del:
return this._parseDelStatement();
case KeywordType.Yield:
return this._parseYieldExpression();
case KeywordType.Lazy: {
// Lazy is considered a "soft" keyword, so we will treat it
// as an identifier if it is not followed by "import" or "from".
const peekToken1 = this._peekToken(1);
if (
peekToken1.type === TokenType.Keyword &&
((peekToken1 as KeywordToken).keywordType === KeywordType.Import ||
(peekToken1 as KeywordToken).keywordType === KeywordType.From)
) {
return this._parseLazyImportStatement();
}
break;
}
case KeywordType.Type: {
// Type is considered a "soft" keyword, so we will treat it
// as an identifier if it is followed by an unexpected token.
const peekToken1 = this._peekToken(1);
const peekToken2 = this._peekToken(2);
let isInvalidTypeToken = true;
if (
peekToken1.type === TokenType.Identifier ||
(peekToken1.type === TokenType.Keyword && KeywordToken.isSoftKeyword(peekToken1 as KeywordToken))
) {
if (peekToken2.type === TokenType.OpenBracket) {
isInvalidTypeToken = false;
} else if (
peekToken2.type === TokenType.Operator &&
(peekToken2 as OperatorToken).operatorType === OperatorType.Assign
) {
isInvalidTypeToken = false;
}
}
if (!isInvalidTypeToken) {
return this._parseTypeAliasStatement();
}
break;
}
}
return this._parseExpressionStatement();
}
// Viper 扩展:判定语句起始处是否为 `typedef <Name> =` 形状。
// `typedef` 是软关键字:仅当后跟「标识符」再跟 `=` 时才是 typedef 语句,
// 否则保持普通标识符解析(`typedef = 1` 是赋值、`typedef(x)` 是调用)。
private _isAtViperTypedefStatement(): boolean {
if (this._parseOptions.dialect !== 'viper') {
return false;
}
const keywordToken = this._peekToken();
if (keywordToken.type !== TokenType.Identifier || (keywordToken as IdentifierToken).value !== 'typedef') {
return false;
}
if (this._peekToken(1).type !== TokenType.Identifier) {
return false;
}
const assignToken = this._peekToken(2);
return (
assignToken.type === TokenType.Operator &&
(assignToken as OperatorToken).operatorType === OperatorType.Assign
);
}
// Viper 扩展:`typedef T1 = T2` 语句(与 `T1: t.CTypedef = T2` 完全等价)。
// 解析层脱糖为「带注解的赋值」节点:左值 `T1`,注解为合成的零长 `t.CTypedef`,
// 右值为 T2 —— 与手写 `T1: t.CTypedef = T2` 的 AST 同形,从而复用既有的
// TypeAlias 语义(绑定、类型求值、hover、语义高亮均一致)。
//
// 取舍:不依赖源码文件 `import t`。合成 `t.CTypedef` 并不真去解析 `t` 符号:
// 类型求值层识别 t.CTypedef 只看成员名文本(见 typeEvaluator 的
// isExplicitTypeAliasDeclaration / hoverProvider 的 CTypedef 快速路径),
// 合成 `t` 标记 isViperTypedef 后,未 import t 时静默退化为 Unknown
// (与 `[SIZE]T` 数组脱糖合成 `t.CArray` 的取舍相同)。
private _parseViperTypedefStatement(): ExpressionNode {
const typedefToken = this._getNextToken();
assert(typedefToken.type === TokenType.Identifier);
const nameToken = this._getNextToken() as IdentifierToken;
const nameNode = NameNode.create(nameToken);
// 消费 '='(_isAtViperTypedefStatement 已确认存在)。
this._getNextToken();
// 右值与 `T1: t.CTypedef = T2` 完全同路解析。
const rightExpr =
this._tryParseYieldExpression() ??
this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ false,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedAssignRightHandExpr()
);
// 合成 `t.CTypedef` 注解:零长度、位置紧贴别名名之后(满足
// "子节点按位置递增且不重叠" 的 AST 不变量),故不产生语义高亮
// (semanticTokenProvider 跳过 length <= 0 的名字),也不参与位置命中。
const syntheticStart = nameToken.start + nameToken.length;
const syntheticTNameNode = NameNode.create(
IdentifierToken.create(syntheticStart, 0, 't', /* comments */ undefined)
);
syntheticTNameNode.d.isViperTypedef = true;
const cTypedefNameNode = NameNode.create(
IdentifierToken.create(syntheticStart, 0, 'CTypedef', /* comments */ undefined)
);
const annotationNode = MemberAccessNode.create(syntheticTNameNode, cTypedefNameNode);
const leftExpr = TypeAnnotationNode.create(nameNode, annotationNode);
return AssignmentNode.create(leftExpr, rightExpr);
}
private _makeExpressionOrTuple(
exprListResult: ListResult<ExpressionNode>,
enclosedInParens: boolean
): ExpressionNode {
// A single-element tuple with no trailing comma is simply an expression
// that's surrounded by parens.
if (exprListResult.list.length === 1 && !exprListResult.trailingComma) {
if (exprListResult.list[0].nodeType === ParseNodeType.Unpack) {
this._addSyntaxError(LocMessage.unpackOperatorNotAllowed(), exprListResult.list[0]);
}
return exprListResult.list[0];
}
// To accommodate empty tuples ("()"), we will reach back to get
// the opening parenthesis as the opening token.
const tupleStartRange: TextRange =
exprListResult.list.length > 0 ? exprListResult.list[0] : this._peekToken(-1);
const tupleNode = TupleNode.create(tupleStartRange, enclosedInParens);
tupleNode.d.items = exprListResult.list;
if (exprListResult.list.length > 0) {
exprListResult.list.forEach((expr) => {
expr.parent = tupleNode;
});
extendRange(tupleNode, exprListResult.list[exprListResult.list.length - 1]);
}
return tupleNode;
}
private _parseExpressionListAsPossibleTuple(
errorCategory: ErrorExpressionCategory,
getErrorString: () => string,
errorToken: Token
): ExpressionNode {
if (this._isNextTokenNeverExpression()) {
this._addSyntaxError(getErrorString(), errorToken);
return ErrorNode.create(errorToken, errorCategory);
}
const exprListResult = this._parseExpressionList(/* allowStar */ true);
if (exprListResult.parseError) {
return exprListResult.parseError;
}
return this._makeExpressionOrTuple(exprListResult, /* enclosedInParens */ false);
}
private _parseTestListAsExpression(
errorCategory: ErrorExpressionCategory,
getErrorString: () => string
): ExpressionNode {
if (this._isNextTokenNeverExpression()) {
return this._handleExpressionParseError(errorCategory, getErrorString());
}
const exprListResult = this._parseTestExpressionList();
if (exprListResult.parseError) {
return exprListResult.parseError;
}
return this._makeExpressionOrTuple(exprListResult, /* enclosedInParens */ false);
}
private _parseTestOrStarListAsExpression(
allowAssignmentExpression: boolean,
allowMultipleUnpack: boolean,
errorCategory: ErrorExpressionCategory,
getErrorString: () => string
): ExpressionNode {
if (this._isNextTokenNeverExpression()) {
return this._handleExpressionParseError(errorCategory, getErrorString());
}
const exprListResult = this._parseTestOrStarExpressionList(allowAssignmentExpression, allowMultipleUnpack);
if (exprListResult.parseError) {
return exprListResult.parseError;
}
return this._makeExpressionOrTuple(exprListResult, /* enclosedInParens */ false);
}
private _parseExpressionList(allowStar: boolean): ListResult<ExpressionNode> {
return this._parseExpressionListGeneric(() => this._parseExpression(allowStar));
}
// testlist: test (',' test)* [',']
private _parseTestExpressionList(): ListResult<ExpressionNode> {
return this._parseExpressionListGeneric(() => this._parseTestExpression(/* allowAssignmentExpression */ false));
}
private _parseTestOrStarExpressionList(
allowAssignmentExpression: boolean,
allowMultipleUnpack: boolean
): ListResult<ExpressionNode> {
const exprListResult = this._parseExpressionListGeneric(() =>
this._parseTestOrStarExpression(allowAssignmentExpression)
);
if (!allowMultipleUnpack && !exprListResult.parseError) {
let sawStar = false;
for (const expr of exprListResult.list) {
if (expr.nodeType === ParseNodeType.Unpack) {
if (sawStar) {
this._addSyntaxError(LocMessage.duplicateUnpack(), expr);
break;
}
sawStar = true;
}
}
}
return exprListResult;
}
// exp_or_star: expr | star_expr
// expr: xor_expr ('|' xor_expr)*
// star_expr: '*' expr
private _parseExpression(allowUnpack: boolean): ExpressionNode {
const startToken = this._peekToken();
if (allowUnpack && this._consumeTokenIfOperator(OperatorType.Multiply)) {
return UnpackNode.create(startToken, this._parseExpression(/* allowUnpack */ false));
}
return this._parseBitwiseOrExpression();
}
// 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) {
return this._parseLambdaExpression();
}
const ifExpr = this._parseAssignmentExpression(!allowAssignmentExpression);
if (ifExpr.nodeType === ParseNodeType.Error) {
return ifExpr;
}
if (!this._consumeTokenIfKeyword(KeywordType.If)) {
return ifExpr;
}
const testExpr = this._parseOrTest();
if (testExpr.nodeType === ParseNodeType.Error) {
return testExpr;
}
if (!this._consumeTokenIfKeyword(KeywordType.Else)) {
return TernaryNode.create(
ifExpr,
testExpr,
this._handleExpressionParseError(ErrorExpressionCategory.MissingElse, LocMessage.expectedElse())
);
}
const elseExpr = this._parseTestExpression(/* allowAssignmentExpression */ true);
return TernaryNode.create(ifExpr, testExpr, elseExpr);
}
// assign_expr: NAME := test
private _parseAssignmentExpression(disallowAssignmentExpression = false) {
const leftExpr = this._parseOrTest();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
if (leftExpr.nodeType !== ParseNodeType.Name) {
return leftExpr;
}
const walrusToken = this._peekToken();
if (!this._consumeTokenIfOperator(OperatorType.Walrus)) {
return leftExpr;
}
if (!this._assignmentExpressionsAllowed || disallowAssignmentExpression) {
this._addSyntaxError(LocMessage.walrusNotAllowed(), walrusToken);
}
if (PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_8)) {
this._addSyntaxError(LocMessage.walrusIllegal(), walrusToken);
}
const rightExpr = this._parseTestExpression(/* allowAssignmentExpression */ false);
return AssignmentExpressionNode.create(leftExpr, walrusToken, rightExpr);
}
// or_test: and_test ('or' and_test)*
private _parseOrTest(): ExpressionNode {
let leftExpr = this._parseAndTest();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
while (true) {
const peekToken = this._peekToken();
if (!this._consumeTokenIfKeyword(KeywordType.Or)) {
break;
}
const rightExpr = this._parseAndTest();
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, OperatorType.Or);
}
return leftExpr;
}
// and_test: not_test ('and' not_test)*
private _parseAndTest(): ExpressionNode {
let leftExpr = this._parseNotTest();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
while (true) {
const peekToken = this._peekToken();
if (!this._consumeTokenIfKeyword(KeywordType.And)) {
break;
}
const rightExpr = this._parseNotTest();
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, OperatorType.And);
}
return leftExpr;
}
// not_test: 'not' not_test | comparison
private _parseNotTest(): ExpressionNode {
const notToken = this._peekToken();
if (this._consumeTokenIfKeyword(KeywordType.Not)) {
const notExpr = this._parseNotTest();
return this._createUnaryOperationNode(notToken, notExpr, OperatorType.Not);
}
return this._parseComparison();
}
// comparison: expr (comp_op expr)*
// comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
private _parseComparison(): ExpressionNode {
let leftExpr = this._parseBitwiseOrExpression();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
while (true) {
let comparisonOperator: OperatorType | undefined;
const peekToken = this._peekToken();
if (Tokenizer.isOperatorComparison(this._peekOperatorType())) {
comparisonOperator = this._peekOperatorType();
if (comparisonOperator === OperatorType.LessOrGreaterThan) {
this._addSyntaxError(LocMessage.operatorLessOrGreaterDeprecated(), peekToken);
comparisonOperator = OperatorType.NotEquals;
}
this._getNextToken();
} else if (this._consumeTokenIfKeyword(KeywordType.In)) {
comparisonOperator = OperatorType.In;
} else if (this._consumeTokenIfKeyword(KeywordType.Is)) {
if (this._consumeTokenIfKeyword(KeywordType.Not)) {
comparisonOperator = OperatorType.IsNot;
} else {
comparisonOperator = OperatorType.Is;
}
} else if (this._peekKeywordType() === KeywordType.Not) {
const tokenAfterNot = this._peekToken(1);
if (
tokenAfterNot.type === TokenType.Keyword &&
(tokenAfterNot as KeywordToken).keywordType === KeywordType.In
) {
this._getNextToken();
this._getNextToken();
comparisonOperator = OperatorType.NotIn;
}
}
if (comparisonOperator === undefined) {
break;
}
const rightExpr = this._parseComparison();
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, comparisonOperator);
}
return leftExpr;
}
// expr: xor_expr ('|' xor_expr)*
private _parseBitwiseOrExpression(): ExpressionNode {
let leftExpr = this._parseBitwiseXorExpression();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
while (true) {
const peekToken = this._peekToken();
if (!this._consumeTokenIfOperator(OperatorType.BitwiseOr)) {
break;
}
const rightExpr = this._parseBitwiseXorExpression();
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, OperatorType.BitwiseOr);
}
return leftExpr;
}
// xor_expr: and_expr ('^' and_expr)*
private _parseBitwiseXorExpression(): ExpressionNode {
let leftExpr = this._parseBitwiseAndExpression();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
while (true) {
const peekToken = this._peekToken();
if (!this._consumeTokenIfOperator(OperatorType.BitwiseXor)) {
break;
}
const rightExpr = this._parseBitwiseAndExpression();
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, OperatorType.BitwiseXor);
}
return leftExpr;
}
// and_expr: shift_expr ('&' shift_expr)*
private _parseBitwiseAndExpression(): ExpressionNode {
let leftExpr = this._parseShiftExpression();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
while (true) {
const peekToken = this._peekToken();
if (!this._consumeTokenIfOperator(OperatorType.BitwiseAnd)) {
break;
}
const rightExpr = this._parseShiftExpression();
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, OperatorType.BitwiseAnd);
}
return leftExpr;
}
// shift_expr: arith_expr (('<<'|'>>') arith_expr)*
private _parseShiftExpression(): ExpressionNode {
let leftExpr = this._parseArithmeticExpression();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
let peekToken = this._peekToken();
let nextOperator = this._peekOperatorType();
while (nextOperator === OperatorType.LeftShift || nextOperator === OperatorType.RightShift) {
this._getNextToken();
const rightExpr = this._parseArithmeticExpression();
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, nextOperator);
peekToken = this._peekToken();
nextOperator = this._peekOperatorType();
}
return leftExpr;
}
// arith_expr: term (('+'|'-') term)*
private _parseArithmeticExpression(): ExpressionNode {
let leftExpr = this._parseArithmeticTerm();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
let peekToken = this._peekToken();
let nextOperator = this._peekOperatorType();
while (nextOperator === OperatorType.Add || nextOperator === OperatorType.Subtract) {
this._getNextToken();
const rightExpr = this._parseArithmeticTerm();
if (rightExpr.nodeType === ParseNodeType.Error) {
return rightExpr;
}
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, nextOperator);
peekToken = this._peekToken();
nextOperator = this._peekOperatorType();
}
return leftExpr;
}
// term: factor (('*'|'@'|'/'|'%'|'//') factor)*
private _parseArithmeticTerm(): ExpressionNode {
let leftExpr = this._parseArithmeticFactor();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
let peekToken = this._peekToken();
let nextOperator = this._peekOperatorType();
while (
nextOperator === OperatorType.Multiply ||
nextOperator === OperatorType.MatrixMultiply ||
nextOperator === OperatorType.Divide ||
nextOperator === OperatorType.Mod ||
nextOperator === OperatorType.FloorDivide
) {
// Viper 扩展:后缀 '*' 是 C 指针类型(T* = CPtr[T]),不是二元乘法。
// 若该星号串后紧跟"无右操作数"位置(']' / ',' / ')' / ':' /
// '=' / 换行 / ';' / 文件结束),就地按指针后缀消费。此判定不限于类型注解
// 上下文:调用实参中的类型名(如 `va_arg(ap, int64*)`)同样适用。
// 若不在后缀位置:类型注解上下文停止解析,交由
// _parseTypeAnnotation/_parseTypeAliasStatement 的后缀处理消费;
// 其他上下文则按真实二元乘法继续解析(`a * b`、`N * 2` 行为不变)。
if (
this._parseOptions.dialect === 'viper' &&
nextOperator === OperatorType.Multiply
) {
if (!this._isParsingViperTypeDescriptorInner && this._isAtViperPointerTypeSuffix()) {
leftExpr = this._parseViperPointerTypeSuffix(leftExpr);
peekToken = this._peekToken();
nextOperator = this._peekOperatorType();
continue;
}
if (this._isParsingTypeAnnotation) {
break;
}
}
this._getNextToken();
const rightExpr = this._parseArithmeticFactor();
leftExpr = this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, nextOperator);
peekToken = this._peekToken();
nextOperator = this._peekOperatorType();
}
return leftExpr;
}
// 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 ||
isViperUnaryOp
) {
this._getNextToken();
const expression = this._parseArithmeticFactor();
return this._createUnaryOperationNode(nextToken, expression, nextOperator);
}
const leftExpr = this._parseAtomExpression();
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
const peekToken = this._peekToken();
const nextPowerOperator = this._peekOperatorType();
// Viper 扩展:'**' 是二级指针类型后缀(T** = CPtr[CPtr[T]]),
// 词法化为 Power 算子('**'),不是幂运算。若其后紧跟"无右操作数"位置
// (如 `list[ast.AST**]`、`x: int**`、调用实参 `va_arg(ap, short**)`),
// 就地按指针后缀消费。此判定不限于类型注解上下文;
// 若不在后缀位置:类型注解上下文跳过幂运算分支,交由
// _parseTypeAnnotation/_parseTypeAliasStatement 的后缀处理消费;
// 其他上下文则按真实幂运算解析(`a ** b` 行为不变)。
if (nextPowerOperator === OperatorType.Power && this._parseOptions.dialect === 'viper') {
if (this._isAtViperPointerTypeSuffix()) {
return this._parseViperPointerTypeSuffix(leftExpr);
}
if (this._isParsingTypeAnnotation) {
return leftExpr;
}
}
if (nextPowerOperator === OperatorType.Power) {
this._getNextToken();
const rightExpr = this._parseArithmeticFactor();
return this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, OperatorType.Power);
}
return leftExpr;
}
// Determines whether the expression refers to a type exported by the typing
// or typing_extensions modules. We can directly evaluate the types at binding
// time. We assume here that the code isn't making use of some custom type alias
// to refer to the typing types.
private _isTypingAnnotation(typeAnnotation: ExpressionNode, name: string): boolean {
if (typeAnnotation.nodeType === ParseNodeType.Name) {
const alias = this._typingSymbolAliases.get(typeAnnotation.d.value);
if (alias === name) {
return true;
}
} else if (typeAnnotation.nodeType === ParseNodeType.MemberAccess) {
if (typeAnnotation.d.leftExpr.nodeType === ParseNodeType.Name && typeAnnotation.d.member.d.value === name) {
const baseName = typeAnnotation.d.leftExpr.d.value;
return this._typingImportAliases.some((alias) => alias === baseName);
}
}
return false;
}
// atom_expr: ['await'] atom trailer*
// trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
private _parseAtomExpression(): ExpressionNode {
let awaitToken: KeywordToken | undefined;
if (this._peekKeywordType() === KeywordType.Await) {
awaitToken = this._getKeywordToken(KeywordType.Await);
if (PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_5)) {
this._addSyntaxError(LocMessage.awaitIllegal(), awaitToken);
}
}
// Viper 扩展:值表达式中的 `volatile` 前缀 —— `a: int = volatile f()` /
// `a: int = volatile 结构体()`。语义:该调用(函数调用或结构体构造的
// __new__ / __before_init__ / __init__)被标记为 volatile(阻止编译器优化,
// 加 LLVM volatile)。仅 LSP 侧解析(TPC/TPV 不处理该形态):
// 消费 `volatile` 后必须紧跟「调用」形态,否则报语法错误;
// `volatile = 2` 这类裸用因下一 token 非表达式起点而不进入本分支,仍照旧失败。
if (this._isAtViperVolatileValuePrefix()) {
return this._parseViperVolatileValue();
}
let atomExpression = this._parseAtom();
if (atomExpression.nodeType === ParseNodeType.Error) {
return atomExpression;
}
// Consume trailers.
while (true) {
// Viper 扩展:`T*(x)` / `T**(x)` —— 星号串与基类型、'(' 均**紧贴**时按 C 风格
// 指针强转调用解析(判据与 TPC ExprParser._AtPtrSuffixCall 同源)。
// 先把星串消费为「指针类型后缀」,紧接的 '(' 由下一轮 trailer 按调用处理,
// 产物与既有 `(T*)x` 逐字同形:Call(func = 标记的指针后缀, args = 实参)。
// 因此 `T*(x)[0] = v` 这类后续下标(乃至赋值目标)自然成立——TPC 同样把
// `T*(x)` 解析成调用,故 `uint32*(info.vendor_id)[0] = res.ebx` 是合法赋值目标。
if (this._isAtViperPointerSuffixCastCall(atomExpression)) {
atomExpression = this._parseViperPointerTypeSuffix(atomExpression);
continue;
}
// Is it a function call?
const startOfTrailerToken = this._peekToken();
if (this._consumeTokenIfType(TokenType.OpenParenthesis)) {
// Generally, function calls are not allowed within type annotations,
// but they are permitted in "Annotated" annotations.
const wasParsingTypeAnnotation = this._isParsingTypeAnnotation;
this._isParsingTypeAnnotation = false;
const argListResult = this._parseArgList();
const callNode = CallNode.create(atomExpression, argListResult.args, argListResult.trailingComma);
if (argListResult.args.length > 1 || argListResult.trailingComma) {
argListResult.args.forEach((arg) => {
if (arg.d.valueExpr.nodeType === ParseNodeType.Comprehension) {
if (!arg.d.valueExpr.d.hasParens) {
this._addSyntaxError(LocMessage.generatorNotParenthesized(), arg.d.valueExpr);
}
}
});
}
const nextToken = this._peekToken();
let isArgListTerminated = false;
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
this._addSyntaxError(LocMessage.expectedCloseParen(), startOfTrailerToken);
// Consume the remainder of tokens on the line for error
// recovery.
this._consumeTokensUntilType([TokenType.NewLine]);
// Extend the node's range to include the rest of the line.
// This helps the signatureHelpProvider.
extendRange(callNode, this._peekToken());
} else {
extendRange(callNode, nextToken);
isArgListTerminated = true;
}
this._isParsingTypeAnnotation = wasParsingTypeAnnotation;
const maxDepth = this._maxChildDepthMap.get(atomExpression.id) ?? 0;
if (maxDepth >= maxChildNodeDepth) {
atomExpression = ErrorNode.create(callNode, ErrorExpressionCategory.MaxDepthExceeded);
this._addSyntaxError(LocMessage.maxParseDepthExceeded(), atomExpression);
} else {
atomExpression = callNode;
this._maxChildDepthMap.set(callNode.id, maxDepth + 1);
}
// If the argument list wasn't terminated, break out of the loop
if (!isArgListTerminated) {
break;
}
} else if (this._consumeTokenIfType(TokenType.OpenBracket)) {
// Is it an index operator?
// This is an unfortunate hack that's necessary to accommodate 'Literal'
// and 'Annotated' type annotations properly. We need to suspend treating
// strings as type annotations within a Literal or Annotated subscript.
const wasParsingIndexTrailer = this._isParsingIndexTrailer;
const wasParsingTypeAnnotation = this._isParsingTypeAnnotation;
if (
this._isTypingAnnotation(atomExpression, 'Literal') ||
this._isTypingAnnotation(atomExpression, 'Annotated')
) {
this._isParsingTypeAnnotation = false;
}
this._isParsingIndexTrailer = true;
const subscriptList = this._parseSubscriptList();
this._isParsingTypeAnnotation = wasParsingTypeAnnotation;
this._isParsingIndexTrailer = wasParsingIndexTrailer;
const closingToken = this._peekToken();
const indexNode = IndexNode.create(
atomExpression,
subscriptList.list,
subscriptList.trailingComma,
closingToken
);
extendRange(indexNode, indexNode);
if (!this._consumeTokenIfType(TokenType.CloseBracket)) {
// Handle the error case, but don't use the error node in this
// case because it creates problems for the completion provider.
this._handleExpressionParseError(
ErrorExpressionCategory.MissingIndexCloseBracket,
LocMessage.expectedCloseBracket(),
startOfTrailerToken,
indexNode
);
}
const maxDepth = this._maxChildDepthMap.get(atomExpression.id) ?? 0;
if (maxDepth >= maxChildNodeDepth) {
atomExpression = ErrorNode.create(indexNode, ErrorExpressionCategory.MaxDepthExceeded);
this._addSyntaxError(LocMessage.maxParseDepthExceeded(), atomExpression);
} else {
atomExpression = indexNode;
this._maxChildDepthMap.set(indexNode.id, maxDepth + 1);
}
} else if (this._consumeTokenIfType(TokenType.Dot)) {
// Is it a member access?
const memberName = this._getTokenIfIdentifier();
if (!memberName) {
return this._handleExpressionParseError(
ErrorExpressionCategory.MissingMemberAccessName,
LocMessage.expectedMemberName(),
startOfTrailerToken,
atomExpression,
[TokenType.Keyword]
);
}
const memberAccessNode = MemberAccessNode.create(atomExpression, NameNode.create(memberName));
const maxDepth = this._maxChildDepthMap.get(atomExpression.id) ?? 0;
if (maxDepth >= maxChildNodeDepth) {
atomExpression = ErrorNode.create(memberAccessNode, ErrorExpressionCategory.MaxDepthExceeded);
this._addSyntaxError(LocMessage.maxParseDepthExceeded(), atomExpression);
} else {
atomExpression = memberAccessNode;
this._maxChildDepthMap.set(memberAccessNode.id, maxDepth + 1);
}
} else {
break;
}
}
if (awaitToken) {
return AwaitNode.create(awaitToken, atomExpression);
}
return atomExpression;
}
// subscriptlist: subscript (',' subscript)* [',']
private _parseSubscriptList(): SubscriptListResult {
const argList: ArgumentNode[] = [];
let sawKeywordArg = false;
let trailingComma = false;
while (true) {
const firstToken = this._peekToken();
if (firstToken.type !== TokenType.Colon && this._isNextTokenNeverExpression()) {
break;
}
let argType = ArgCategory.Simple;
if (this._consumeTokenIfOperator(OperatorType.Multiply)) {
argType = ArgCategory.UnpackedList;
} else if (this._consumeTokenIfOperator(OperatorType.Power)) {
argType = ArgCategory.UnpackedDictionary;
}
const startOfSubscriptIndex = this._tokenIndex;
// Viper 扩展:下标实参位置的 `[SIZE]T`(如 list[[4]int])先按数组类型解析。
let valueExpr =
argType === ArgCategory.Simple && this._isAtViperSubscriptBracketArrayType()
? this._parseViperBracketArrayType()
: this._parsePossibleSlice();
let nameIdentifier: IdentifierToken | undefined;
// Is this a keyword argument?
if (argType === ArgCategory.Simple) {
if (this._consumeTokenIfOperator(OperatorType.Assign)) {
const nameExpr = valueExpr;
valueExpr = this._parsePossibleSlice();
if (nameExpr.nodeType === ParseNodeType.Name) {
nameIdentifier = nameExpr.d.token;
} else {
this._addSyntaxError(LocMessage.expectedParamName(), nameExpr);
}
} else if (
valueExpr.nodeType === ParseNodeType.Name &&
this._peekOperatorType() === OperatorType.Walrus
) {
this._tokenIndex = startOfSubscriptIndex;
valueExpr = this._parseTestExpression(/* allowAssignmentExpression */ true);
// Python 3.10 and newer allow assignment expressions to be used inside of a subscript.
if (
!this._parseOptions.isStubFile &&
PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_10)
) {
this._addSyntaxError(LocMessage.assignmentExprInSubscript(), valueExpr);
}
}
}
const argNode = ArgumentNode.create(firstToken, valueExpr, argType);
if (nameIdentifier) {
argNode.d.name = NameNode.create(nameIdentifier);
argNode.d.name.parent = argNode;
}
if (argNode.d.name) {
sawKeywordArg = true;
} else if (sawKeywordArg && argNode.d.argCategory === ArgCategory.Simple) {
this._addSyntaxError(LocMessage.positionArgAfterNamedArg(), argNode);
}
argList.push(argNode);
if (argNode.d.name) {
this._addSyntaxError(LocMessage.keywordSubscriptIllegal(), argNode.d.name);
}
if (argType !== ArgCategory.Simple) {
const unpackListAllowed =
this._parseOptions.isStubFile ||
this._isParsingQuotedText ||
PythonVersion.isGreaterOrEqualTo(this._getLanguageVersion(), pythonVersion3_11);
if (argType === ArgCategory.UnpackedList && !unpackListAllowed) {
this._addSyntaxError(LocMessage.unpackedSubscriptIllegal(), argNode);
}
if (argType === ArgCategory.UnpackedDictionary) {
this._addSyntaxError(LocMessage.unpackedDictSubscriptIllegal(), argNode);
}
}
if (!this._consumeTokenIfType(TokenType.Comma)) {
trailingComma = false;
break;
}
trailingComma = true;
}
// An empty subscript list is illegal.
if (argList.length === 0) {
const errorNode = this._handleExpressionParseError(
ErrorExpressionCategory.MissingIndexOrSlice,
LocMessage.expectedSliceIndex(),
/* targetToken */ undefined,
/* childNode */ undefined,
[TokenType.CloseBracket]
);
argList.push(ArgumentNode.create(this._peekToken(), errorNode, ArgCategory.Simple));
}
return {
list: argList,
trailingComma,
};
}
// subscript: test | [test] ':' [test] [sliceop]
// sliceop: ':' [test]
private _parsePossibleSlice(): ExpressionNode {
const firstToken = this._peekToken();
const sliceExpressions: (ExpressionNode | undefined)[] = [undefined, undefined, undefined];
let sliceIndex = 0;
let sawColon = false;
while (true) {
const nextTokenType = this._peekTokenType();
if (nextTokenType === TokenType.CloseBracket || nextTokenType === TokenType.Comma) {
break;
}
if (nextTokenType !== TokenType.Colon) {
// Python 3.10 and newer allow assignment expressions to be used inside of a subscript.
const allowAssignmentExpression =
this._parseOptions.isStubFile ||
PythonVersion.isGreaterOrEqualTo(this._getLanguageVersion(), pythonVersion3_10);
sliceExpressions[sliceIndex] = this._parseTestExpression(allowAssignmentExpression);
}
sliceIndex++;
if (sliceIndex >= 3 || !this._consumeTokenIfType(TokenType.Colon)) {
break;
}
sawColon = true;
}
// If this was a simple expression with no colons return it.
if (!sawColon) {
if (sliceExpressions[0]) {
return sliceExpressions[0];
}
return ErrorNode.create(this._peekToken(), ErrorExpressionCategory.MissingIndexOrSlice);
}
const sliceNode = SliceNode.create(firstToken);
sliceNode.d.startValue = sliceExpressions[0];
if (sliceNode.d.startValue) {
sliceNode.d.startValue.parent = sliceNode;
}
sliceNode.d.endValue = sliceExpressions[1];
if (sliceNode.d.endValue) {
sliceNode.d.endValue.parent = sliceNode;
}
sliceNode.d.stepValue = sliceExpressions[2];
if (sliceNode.d.stepValue) {
sliceNode.d.stepValue.parent = sliceNode;
}
const extension = sliceExpressions[2] || sliceExpressions[1] || sliceExpressions[0];
if (extension) {
extendRange(sliceNode, extension);
}
return sliceNode;
}
// arglist: argument (',' argument)* [',']
private _parseArgList(): ArgListResult {
const argList: ArgumentNode[] = [];
let sawKeywordArg = false;
let sawUnpackedKeywordArg = false;
let trailingComma = false;
while (true) {
const nextTokenType = this._peekTokenType();
if (
nextTokenType === TokenType.CloseParenthesis ||
nextTokenType === TokenType.NewLine ||
nextTokenType === TokenType.EndOfStream
) {
break;
}
trailingComma = false;
const arg = this._parseArgument();
if (arg.d.name) {
sawKeywordArg = true;
} else {
if (sawKeywordArg && arg.d.argCategory === ArgCategory.Simple) {
this._addSyntaxError(LocMessage.positionArgAfterNamedArg(), arg);
}
if (sawUnpackedKeywordArg && arg.d.argCategory !== ArgCategory.UnpackedDictionary) {
this._addSyntaxError(LocMessage.positionArgAfterUnpackedDictArg(), arg);
}
}
if (arg.d.argCategory === ArgCategory.UnpackedDictionary) {
sawUnpackedKeywordArg = true;
}
argList.push(arg);
if (!this._consumeTokenIfType(TokenType.Comma)) {
break;
}
trailingComma = true;
}
return { args: argList, trailingComma };
}
// argument: ( test [comp_for] |
// test '=' test |
// '**' test |
// '*' test )
private _parseArgument(): ArgumentNode {
const firstToken = this._peekToken();
let argType = ArgCategory.Simple;
if (this._consumeTokenIfOperator(OperatorType.Multiply)) {
argType = ArgCategory.UnpackedList;
} else if (this._consumeTokenIfOperator(OperatorType.Power)) {
argType = ArgCategory.UnpackedDictionary;
}
let valueExpr = this._parseTestExpression(/* allowAssignmentExpression */ true);
let nameIdentifier: IdentifierToken | undefined;
if (argType === ArgCategory.Simple) {
if (this._consumeTokenIfOperator(OperatorType.Assign)) {
const nameExpr = valueExpr;
valueExpr = this._parseTestExpression(/* allowAssignmentExpression */ false);
if (nameExpr.nodeType === ParseNodeType.Name) {
nameIdentifier = nameExpr.d.token;
} else {
this._addSyntaxError(LocMessage.expectedParamName(), nameExpr);
}
} else {
const comprehension = this._tryParseComprehension(valueExpr, /* isGenerator */ true);
if (comprehension) {
valueExpr = comprehension;
}
}
}
const argNode = ArgumentNode.create(firstToken, valueExpr, argType);
if (nameIdentifier) {
argNode.d.name = NameNode.create(nameIdentifier);
argNode.d.name.parent = argNode;
}
return argNode;
}
// atom: ('(' [yield_expr | testlist_comp] ')' |
// '[' [testlist_comp] ']' |
// '{' [dictorsetmaker] '}' |
// NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False' | '__debug__')
private _parseAtom(): ExpressionNode {
const nextToken = this._peekToken();
if (nextToken.type === TokenType.Ellipsis) {
return EllipsisNode.create(this._getNextToken());
}
if (nextToken.type === TokenType.Number) {
return NumberNode.create(this._getNextToken() as NumberToken);
}
if (nextToken.type === TokenType.Identifier) {
const identifierToken = this._getNextToken() as IdentifierToken;
// Viper 扩展:注解位置的裸 `callable` 等价 `Callable`(t.Callable /
// typing.Callable),支持 `callable[[A, B], R]`、`callable[..., R]`、
// `callable[[], R]`。**仅类型注解位置**脱糖:值位置的 `callable(x)`
// 仍是内置函数(isinstance 风格的 TypeIs 判定)。
// `Callable` 声明在 builtins stub 中(t.CTypedef = t.Callable),
// 故不要求源码显式 `import t`。
if (
this._parseOptions.dialect === 'viper' &&
this._isParsingTypeAnnotation &&
identifierToken.value === 'callable'
) {
return NameNode.create(
IdentifierToken.create(
identifierToken.start,
identifierToken.length,
'Callable',
identifierToken.comments
)
);
}
return NameNode.create(identifierToken);
}
if (nextToken.type === TokenType.String || nextToken.type === TokenType.FStringStart) {
return this._parseStringList();
}
if (nextToken.type === TokenType.Backtick) {
this._getNextToken();
// Atoms with backticks are no longer allowed in Python 3.x, but they
// were a thing in Python 2.x. We'll parse them to improve parse recovery
// and emit an error.
this._addSyntaxError(LocMessage.backticksIllegal(), nextToken);
const expressionNode = this._parseTestListAsExpression(ErrorExpressionCategory.MissingExpression, () =>
LocMessage.expectedExpr()
);
this._consumeTokenIfType(TokenType.Backtick);
return expressionNode;
}
if (nextToken.type === TokenType.OpenParenthesis) {
const possibleTupleNode = this._parseTupleAtom();
if (
possibleTupleNode.nodeType === ParseNodeType.UnaryOperation ||
possibleTupleNode.nodeType === ParseNodeType.Await ||
possibleTupleNode.nodeType === ParseNodeType.BinaryOperation
) {
// Mark binary expressions as parenthesized so we don't attempt
// to use comparison chaining, which isn't appropriate when the
// expression is parenthesized. Unary and await expressions
// are also marked to be able to display them unambiguously.
possibleTupleNode.d.hasParens = true;
}
if (
possibleTupleNode.nodeType === ParseNodeType.StringList ||
possibleTupleNode.nodeType === ParseNodeType.Comprehension ||
possibleTupleNode.nodeType === ParseNodeType.AssignmentExpression
) {
possibleTupleNode.d.hasParens = true;
}
// Viper 扩展:`(T)x` / `(T*)x` —— 括号内是类型原子(Name / MemberAccess /
// Subscript / 指针类型后缀 T*),其后紧跟操作数时按 C 强转解析为调用,
// 语义等价于已支持的 `T(x)` / `(T)(x)` 通路。
// 仅当括号内为类型原子形态才生效,故 `(a)*b`、`(a) * (b)` 等乘法、
// `(*p)x`(前缀解引用)完全不受影响;`(T*)[i]` 因 '[' 非操作数起点仍为下标。
const viperCastNode = this._tryParseViperPointerCast(possibleTupleNode);
if (viperCastNode) {
return viperCastNode;
}
return possibleTupleNode;
} else if (nextToken.type === TokenType.OpenBracket) {
return this._parseListAtom();
} else if (nextToken.type === TokenType.OpenCurlyBrace) {
return this._parseDictionaryOrSetAtom();
}
if (nextToken.type === TokenType.Keyword) {
const keywordToken = nextToken as KeywordToken;
if (
keywordToken.keywordType === KeywordType.False ||
keywordToken.keywordType === KeywordType.True ||
keywordToken.keywordType === KeywordType.Debug ||
keywordToken.keywordType === KeywordType.None
) {
return ConstantNode.create(this._getNextToken() as KeywordToken);
}
// Make an identifier out of the keyword.
const keywordAsIdentifier = this._getTokenIfIdentifier();
if (keywordAsIdentifier) {
return NameNode.create(keywordAsIdentifier);
}
}
return this._handleExpressionParseError(ErrorExpressionCategory.MissingExpression, LocMessage.expectedExpr());
}
// Allocates a dummy "error expression" and consumes the remainder
// of the tokens on the line for error recovery. A partially-completed
// child node can be passed to help the completion provider determine
// what to do.
private _handleExpressionParseError(
category: ErrorExpressionCategory,
errorMsg: string,
targetToken?: Token,
childNode?: ExpressionNode,
additionalStopTokens?: TokenType[]
): ErrorNode {
this._addSyntaxError(errorMsg, targetToken ?? this._peekToken());
const stopTokens = [TokenType.NewLine];
if (additionalStopTokens) {
appendArray(stopTokens, additionalStopTokens);
}
// Using a token that is not included in the error node creates problems.
// Sibling nodes in parse tree shouldn't overlap each other.
const nextToken = this._peekToken();
const initialRange: TextRange = stopTokens.some((k) => nextToken.type === k)
? targetToken ?? childNode ?? TextRange.create(nextToken.start, /* length */ 0)
: nextToken;
const expr = ErrorNode.create(initialRange, category, childNode);
this._consumeTokensUntilType(stopTokens);
return expr;
}
// lambdef: 'lambda' [varargslist] ':' test
private _parseLambdaExpression(allowConditional = true): LambdaNode {
const lambdaToken = this._getKeywordToken(KeywordType.Lambda);
const argList = this._parseVarArgsList(TokenType.Colon, /* allowAnnotations */ false);
if (!this._consumeTokenIfType(TokenType.Colon)) {
this._addSyntaxError(LocMessage.expectedColon(), this._peekToken());
}
let testExpr: ExpressionNode;
if (allowConditional) {
testExpr = this._parseTestExpression(/* allowAssignmentExpression */ false);
} else {
testExpr = this._tryParseLambdaExpression(/* allowConditional */ false) || this._parseOrTest();
}
const lambdaNode = LambdaNode.create(lambdaToken, testExpr);
lambdaNode.d.params = argList;
argList.forEach((arg) => {
arg.parent = lambdaNode;
});
return lambdaNode;
}
private _tryParseLambdaExpression(allowConditional = true): LambdaNode | undefined {
if (this._peekKeywordType() !== KeywordType.Lambda) {
return undefined;
}
return this._parseLambdaExpression(allowConditional);
}
// ('(' [yield_expr | testlist_comp] ')'
// testlist_comp: (test | star_expr) (comp_for | (',' (test | star_expr))* [','])
private _parseTupleAtom(): ExpressionNode {
const startParen = this._getNextToken();
assert(startParen.type === TokenType.OpenParenthesis);
const yieldExpr = this._tryParseYieldExpression();
if (yieldExpr) {
if (this._peekTokenType() !== TokenType.CloseParenthesis) {
return this._handleExpressionParseError(
ErrorExpressionCategory.MissingTupleCloseParen,
LocMessage.expectedCloseParen(),
startParen,
yieldExpr
);
} else {
extendRange(yieldExpr, this._getNextToken());
}
return yieldExpr;
}
const exprListResult = this._parseTestListWithComprehension(/* isGenerator */ true);
const tupleOrExpression = this._makeExpressionOrTuple(exprListResult, /* enclosedInParens */ true);
extendRange(tupleOrExpression, startParen);
if (this._peekTokenType() !== TokenType.CloseParenthesis) {
return this._handleExpressionParseError(
ErrorExpressionCategory.MissingTupleCloseParen,
LocMessage.expectedCloseParen(),
startParen,
exprListResult.parseError ?? tupleOrExpression
);
} else {
extendRange(tupleOrExpression, this._getNextToken());
}
return tupleOrExpression;
}
// '[' [testlist_comp] ']'
// testlist_comp: (test | star_expr) (comp_for | (',' (test | star_expr))* [','])
private _parseListAtom(): ListNode | ErrorNode {
const startBracket = this._getNextToken();
assert(startBracket.type === TokenType.OpenBracket);
const exprListResult = this._parseTestListWithComprehension(/* isGenerator */ false);
const closeBracket: Token | undefined = this._peekToken();
if (!this._consumeTokenIfType(TokenType.CloseBracket)) {
return this._handleExpressionParseError(
ErrorExpressionCategory.MissingListCloseBracket,
LocMessage.expectedCloseBracket(),
startBracket,
exprListResult.parseError ?? _createList()
);
}
return _createList();
function _createList() {
const listAtom = ListNode.create(startBracket);
if (closeBracket) {
extendRange(listAtom, closeBracket);
}
if (exprListResult.list.length > 0) {
exprListResult.list.forEach((expr) => {
expr.parent = listAtom;
});
extendRange(listAtom, exprListResult.list[exprListResult.list.length - 1]);
}
listAtom.d.items = exprListResult.list;
return listAtom;
}
}
private _parseTestListWithComprehension(isGenerator: boolean): ListResult<ExpressionNode> {
let sawComprehension = false;
return this._parseExpressionListGeneric(
() => {
let expr = this._parseTestOrStarExpression(/* allowAssignmentExpression */ true);
const comprehension = this._tryParseComprehension(expr, isGenerator);
if (comprehension) {
expr = comprehension;
sawComprehension = true;
}
return expr;
},
() => this._isNextTokenNeverExpression(),
() => sawComprehension
);
}
// '{' [dictorsetmaker] '}'
// dictorsetmaker: (
// (dictentry (comp_for | (',' dictentry)* [',']))
// | (setentry (comp_for | (',' setentry)* [',']))
// )
// dictentry: (test ':' test | '**' expr)
// setentry: test | star_expr
private _parseDictionaryOrSetAtom(): DictionaryNode | SetNode {
const startBrace = this._getNextToken();
assert(startBrace.type === TokenType.OpenCurlyBrace);
const dictionaryEntries: DictionaryEntryNode[] = [];
const setEntries: ExpressionNode[] = [];
let isDictionary = false;
let isSet = false;
let sawComprehension = false;
let isFirstEntry = true;
let trailingCommaToken: Token | undefined;
while (true) {
if (this._peekTokenType() === TokenType.CloseCurlyBrace) {
break;
}
trailingCommaToken = undefined;
let doubleStarExpression: ExpressionNode | undefined;
let keyExpression: ExpressionNode | undefined;
let valueExpression: ExpressionNode | undefined;
const doubleStar = this._peekToken();
if (this._consumeTokenIfOperator(OperatorType.Power)) {
doubleStarExpression = this._parseExpression(/* allowUnpack */ false);
} else {
// A dictionary key is never a forward-reference type annotation, even
// when the dictionary appears within a type annotation (e.g. the field
// names of an inline TypedDict such as `TypedDict[{'x': int}]`). Suspend
// type-annotation parsing for the key so its string isn't parsed into a
// forward-reference expression. The value remains a type annotation and
// must continue to allow forward references.
const wasParsingTypeAnnotation = this._isParsingTypeAnnotation;
this._isParsingTypeAnnotation = false;
keyExpression = this._parseTestOrStarExpression(/* allowAssignmentExpression */ true);
this._isParsingTypeAnnotation = wasParsingTypeAnnotation;
// Allow walrus operators in this context only for Python 3.10 and newer.
// Older versions of Python generated a syntax error in this context.
let isWalrusAllowed = PythonVersion.isGreaterOrEqualTo(this._getLanguageVersion(), pythonVersion3_10);
if (this._consumeTokenIfType(TokenType.Colon)) {
valueExpression = this._parseTestExpression(/* allowAssignmentExpression */ false);
isWalrusAllowed = false;
}
if (
!isWalrusAllowed &&
keyExpression.nodeType === ParseNodeType.AssignmentExpression &&
!keyExpression.d.hasParens
) {
this._addSyntaxError(LocMessage.walrusNotAllowed(), keyExpression.d.walrusToken);
}
}
if (keyExpression && valueExpression) {
if (keyExpression.nodeType === ParseNodeType.Unpack) {
this._addSyntaxError(LocMessage.unpackInDict(), keyExpression);
}
if (isSet) {
this._addSyntaxError(LocMessage.keyValueInSet(), valueExpression);
} else {
const keyEntryNode = DictionaryKeyEntryNode.create(keyExpression, valueExpression);
let dictEntry: DictionaryEntryNode = keyEntryNode;
const comprehension = this._tryParseComprehension(keyEntryNode, /* isGenerator */ false);
if (comprehension) {
dictEntry = comprehension;
sawComprehension = true;
if (!isFirstEntry) {
this._addSyntaxError(LocMessage.comprehensionInDict(), dictEntry);
}
}
dictionaryEntries.push(dictEntry);
isDictionary = true;
}
} else if (doubleStarExpression) {
if (isSet) {
this._addSyntaxError(LocMessage.unpackInSet(), doubleStarExpression);
} else {
const listEntryNode = DictionaryExpandEntryNode.create(doubleStarExpression);
extendRange(listEntryNode, doubleStar);
let expandEntryNode: DictionaryEntryNode = listEntryNode;
const comprehension = this._tryParseComprehension(listEntryNode, /* isGenerator */ false);
if (comprehension) {
expandEntryNode = comprehension;
sawComprehension = true;
if (!isFirstEntry) {
this._addSyntaxError(LocMessage.comprehensionInDict(), doubleStarExpression);
}
}
dictionaryEntries.push(expandEntryNode);
isDictionary = true;
}
} else {
assert(keyExpression !== undefined);
if (keyExpression) {
if (isDictionary) {
const missingValueErrorNode = ErrorNode.create(
this._peekToken(),
ErrorExpressionCategory.MissingDictValue
);
const keyEntryNode = DictionaryKeyEntryNode.create(keyExpression, missingValueErrorNode);
dictionaryEntries.push(keyEntryNode);
this._addSyntaxError(LocMessage.dictKeyValuePairs(), keyExpression);
} else {
const comprehension = this._tryParseComprehension(keyExpression, /* isGenerator */ false);
if (comprehension) {
keyExpression = comprehension;
sawComprehension = true;
if (!isFirstEntry) {
this._addSyntaxError(LocMessage.comprehensionInSet(), keyExpression);
}
}
setEntries.push(keyExpression);
isSet = true;
}
}
}
// List comprehension statements always end the list.
if (sawComprehension) {
break;
}
if (this._peekTokenType() !== TokenType.Comma) {
break;
}
trailingCommaToken = this._getNextToken();
isFirstEntry = false;
}
let closeCurlyBrace: Token | undefined = this._peekToken();
if (!this._consumeTokenIfType(TokenType.CloseCurlyBrace)) {
this._addSyntaxError(LocMessage.expectedCloseBrace(), startBrace);
closeCurlyBrace = undefined;
}
if (isSet) {
const setAtom = SetNode.create(startBrace);
if (closeCurlyBrace) {
extendRange(setAtom, closeCurlyBrace);
}
if (setEntries.length > 0) {
extendRange(setAtom, setEntries[setEntries.length - 1]);
}
setEntries.forEach((entry) => {
entry.parent = setAtom;
});
setAtom.d.items = setEntries;
return setAtom;
}
const dictionaryAtom = DictionaryNode.create(startBrace);
if (trailingCommaToken) {
dictionaryAtom.d.trailingCommaToken = trailingCommaToken;
extendRange(dictionaryAtom, trailingCommaToken);
}
if (closeCurlyBrace) {
extendRange(dictionaryAtom, closeCurlyBrace);
}
if (dictionaryEntries.length > 0) {
dictionaryEntries.forEach((entry) => {
entry.parent = dictionaryAtom;
});
extendRange(dictionaryAtom, dictionaryEntries[dictionaryEntries.length - 1]);
}
dictionaryAtom.d.items = dictionaryEntries;
return dictionaryAtom;
}
private _parseExpressionListGeneric<T extends ParseNode = ExpressionNode>(
parser: () => T | ErrorNode,
terminalCheck: () => boolean = () => this._isNextTokenNeverExpression(),
finalEntryCheck: () => boolean = () => false
): ListResult<T> {
let trailingComma = false;
const list: T[] = [];
let parseError: ErrorNode | undefined;
while (true) {
if (terminalCheck()) {
break;
}
const expr = parser();
if (expr.nodeType === ParseNodeType.Error) {
parseError = expr as ErrorNode;
break;
}
list.push(expr);
// Should we stop without checking for a trailing comma?
if (finalEntryCheck()) {
break;
}
if (!this._consumeTokenIfType(TokenType.Comma)) {
trailingComma = false;
break;
}
trailingComma = true;
}
return { trailingComma, list, parseError };
}
// expr_stmt: testlist_star_expr (annassign | augassign (yield_expr | testlist) |
// ('=' (yield_expr | testlist_star_expr))*)
// testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [',']
// annassign: ':' test ['=' (yield_expr | testlist_star_expr)]
// augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' |
// '<<=' | '>>=' | '**=' | '//=')
private _parseExpressionStatement(): ExpressionNode {
let leftExpr = this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ false,
/* allowMultipleUnpack */ false,
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedExpr()
);
let annotationExpr: ExpressionNode | undefined;
if (leftExpr.nodeType === ParseNodeType.Error) {
return leftExpr;
}
// Is this a type annotation assignment?
if (this._consumeTokenIfType(TokenType.Colon)) {
// 保留原始赋值目标(NameNode),后续 `*=` 拆分重包装时避免嵌套 TypeAnnotationNode。
const targetExpr = leftExpr;
annotationExpr = this._parseTypeAnnotation();
leftExpr = TypeAnnotationNode.create(targetExpr, annotationExpr);
if (
!this._parseOptions.isStubFile &&
PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_6)
) {
this._addSyntaxError(LocMessage.varAnnotationIllegal(), annotationExpr);
}
// 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;
}
// This is an unfortunate hack that's necessary to accommodate 'TypeAlias'
// declarations properly. We need to treat this assignment differently than
// most because the expression on the right side is treated like a type
// annotation and therefore allows string-literal forward declarations.
const isTypeAliasDeclaration = this._isTypingAnnotation(annotationExpr, 'TypeAlias');
const wasParsingTypeAnnotation = this._isParsingTypeAnnotation;
if (isTypeAliasDeclaration) {
this._isParsingTypeAnnotation = true;
}
const rightExpr =
this._tryParseYieldExpression() ??
this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ false,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedAssignRightHandExpr()
);
this._isParsingTypeAnnotation = wasParsingTypeAnnotation;
return AssignmentNode.create(leftExpr, rightExpr);
}
// Is this a simple assignment?
if (this._consumeTokenIfOperator(OperatorType.Assign)) {
return this._parseChainAssignments(leftExpr);
}
if (Tokenizer.isOperatorAssignment(this._peekOperatorType())) {
const operatorToken = this._getNextToken() as OperatorToken;
const rightExpr =
this._tryParseYieldExpression() ??
this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ false,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedBinaryRightHandExpr()
);
this._reportConditionalErrorForStarTupleElement(rightExpr, pythonVersion3_9);
// Make a shallow copy of the dest expression but give it a new ID.
const destExpr = Object.assign({}, leftExpr);
destExpr.id = getNextNodeId();
return AugmentedAssignmentNode.create(leftExpr, rightExpr, operatorToken.operatorType, destExpr);
}
return leftExpr;
}
private _parseChainAssignments(leftExpr: ExpressionNode): ExpressionNode {
// Make a list of assignment targets.
const assignmentTargets = [leftExpr];
let rightExpr: ExpressionNode;
while (true) {
rightExpr =
this._tryParseYieldExpression() ??
this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ false,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedAssignRightHandExpr()
);
if (rightExpr.nodeType === ParseNodeType.Error) {
break;
}
// Continue until we've consumed the entire chain.
if (!this._consumeTokenIfOperator(OperatorType.Assign)) {
break;
}
assignmentTargets.push(rightExpr);
}
// Create a tree of assignment expressions starting with the first one.
// The final RHS value is assigned to the targets left to right in Python.
let assignmentNode = AssignmentNode.create(assignmentTargets[0], rightExpr);
// Look for a type annotation comment at the end of the line.
const typeAnnotationComment = this._parseVariableTypeAnnotationComment();
if (typeAnnotationComment) {
if (assignmentTargets.length > 1) {
// Type comments are not allowed for chained assignments for the
// same reason that variable type annotations don't support
// chained assignments. Note that a type comment was used here
// so it can be later reported as an error by the binder.
assignmentNode.d.chainedAnnotationComment = typeAnnotationComment;
} else {
assignmentNode.d.annotationComment = typeAnnotationComment;
assignmentNode.d.annotationComment.parent = assignmentNode;
extendRange(assignmentNode, assignmentNode.d.annotationComment);
}
}
assignmentTargets.forEach((target, index) => {
if (index > 0) {
assignmentNode = AssignmentNode.create(target, assignmentNode);
}
});
return assignmentNode;
}
private _parseFunctionTypeAnnotation(): FunctionAnnotationNode | undefined {
const openParenToken = this._peekToken();
if (!this._consumeTokenIfType(TokenType.OpenParenthesis)) {
this._addSyntaxError(LocMessage.expectedOpenParen(), this._peekToken());
return undefined;
}
let paramAnnotations: ExpressionNode[] = [];
while (true) {
const nextTokenType = this._peekTokenType();
if (
nextTokenType === TokenType.CloseParenthesis ||
nextTokenType === TokenType.NewLine ||
nextTokenType === TokenType.EndOfStream
) {
break;
}
// Consume "*" or "**" indicators but don't do anything with them.
// (We don't enforce that these are present, absent, or match
// the corresponding parameter types.)
this._consumeTokenIfOperator(OperatorType.Multiply) || this._consumeTokenIfOperator(OperatorType.Power);
const paramAnnotation = this._parseTypeAnnotation();
paramAnnotations.push(paramAnnotation);
if (!this._consumeTokenIfType(TokenType.Comma)) {
break;
}
}
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
this._addSyntaxError(LocMessage.expectedCloseParen(), openParenToken);
this._consumeTokensUntilType([TokenType.Colon]);
}
if (!this._consumeTokenIfType(TokenType.Arrow)) {
this._addSyntaxError(LocMessage.expectedArrow(), this._peekToken());
return undefined;
}
const returnType = this._parseTypeAnnotation();
let isParamListEllipsis = false;
if (paramAnnotations.length === 1 && paramAnnotations[0].nodeType === ParseNodeType.Ellipsis) {
paramAnnotations = [];
isParamListEllipsis = true;
}
return FunctionAnnotationNode.create(openParenToken, isParamListEllipsis, paramAnnotations, returnType);
}
private _parseTypeAnnotation(allowUnpack = false): ExpressionNode {
// Temporary set a flag that indicates we're parsing a type annotation.
const wasParsingTypeAnnotation = this._isParsingTypeAnnotation;
this._isParsingTypeAnnotation = true;
// Allow unpack operators.
const startToken = this._peekToken();
const isUnpack = this._consumeTokenIfOperator(OperatorType.Multiply);
if (
isUnpack &&
allowUnpack &&
!this._parseOptions.isStubFile &&
!this._isParsingQuotedText &&
PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_11)
) {
this._addSyntaxError(LocMessage.unpackedSubscriptIllegal(), startToken);
}
// Viper 扩展:注解主体(类型描述符前缀 / 定长数组 / 普通类型表达式)。
let result = this._parseViperTypeAnnotationCore();
// Viper 扩展:C 指针类型后缀 T* / T** / T***(须在还原 _isParsingTypeAnnotation 之前,
// 使 _parseArithmeticTerm/_parseArithmeticFactor 的星号守卫仍生效)。
// 后缀统一在最外层应用,故 `const int*` 得到 `(CConst | int)*`
// (指针指向被描述符修饰的类型),与 `int*` 中 `*` 绑定到类型的位置一致。
result = this._parseViperPointerTypeSuffix(result);
if (isUnpack) {
result = UnpackNode.create(startToken, result);
}
this._isParsingTypeAnnotation = wasParsingTypeAnnotation;
this._hasTypeAnnotations = true;
return result;
}
// Viper 扩展:注解主体 = 类型描述符前缀(const / volatile)+ 类型表达式。
// 与 _parseTypeAnnotation 分离,使 C 指针后缀 `*` 由调用方在最外层统一应用:
// `const int*` 的脱糖结果 `CConst | int` 先成形,再整体加一层指针后缀。
private _parseViperTypeAnnotationCore(): ExpressionNode {
const attributeNode = this._parseViperAttributePrefix();
if (attributeNode) {
return attributeNode;
}
const descriptorNode = this._parseViperTypeDescriptorPrefix();
if (descriptorNode) {
return descriptorNode;
}
return this._parseViperBracketArrayTypeIfNeeded();
}
// Viper 扩展:注解位置的 `__attribute__(...)` 修饰器前缀。
// 解析层脱糖为等价的联合类型注解,与手写 `T | t.attr.section(".x")` 的 AST 同形
// (编译器按 AST 扫描属性:TPC HandlesAssign._ExtractGlobalAttrs /
// TPV TranslatorGenMixin._scan_global_tattr),从而完全复用既有属性标记语义:
// __attribute__(section(".x")) const int → (t.attr.section(".x") | (CConst | int))
// __attribute__(a, b) int → ((t.attr.a | t.attr.b) | int)
// 与 const / volatile 同族:只作修饰,不改变数据类型。
// 语法与装饰器形态一致(实参用无前缀属性名,逐项脱糖为 `t.attr.<name>`;
// 已是 `t.attr.X` / `c.Attribute(...)` 形态的实参原样保留,允许混写)。
// 注意:与 GCC 的 `__attribute__((a, b))` 不同,Viper 只用**单层**括号。
private _parseViperAttributePrefix(): ExpressionNode | undefined {
if (this._parseOptions.dialect !== 'viper' || !this._isAtViperAttributePrefix(0)) {
return undefined;
}
const attributeToken = this._peekToken();
this._getNextToken(); // 消费 `__attribute__`
this._getNextToken(); // 消费 '('
const argListResult = this._parseArgList();
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
this._addSyntaxError(LocMessage.expectedCloseParen(), this._peekToken());
this._consumeTokensUntilType([TokenType.NewLine]);
}
const markers: ExpressionNode[] = [];
argListResult.args.forEach((arg) => {
if (arg.d.argCategory === ArgCategory.Simple) {
markers.push(this._makeViperAttrMarker(arg.d.valueExpr));
}
});
// 修饰器后必须跟类型(同 const / volatile 的硬关键字约束)。
const nextToken = this._peekToken();
if (!this._isViperTypeAtomStartToken(nextToken) && !this._isAtViperAttributePrefix(0)) {
this._addSyntaxError(
LocMessage.viperTypeDescriptorRequiresType().format({ name: '__attribute__' }),
attributeToken
);
return ErrorNode.create(attributeToken, ErrorExpressionCategory.MissingExpression);
}
// 递归解析被修饰的主体(支持 `__attribute__(...) const int`、
// `const __attribute__(...) int`、多个属性前缀等任意组合)。
const innerType = this._parseViperTypeAnnotationCore();
// 合成 '|' 链('|' token 零长度,源码中不存在):marker1 | marker2 | ... | inner
let modifierNode: ExpressionNode | undefined;
for (const marker of markers) {
if (!modifierNode) {
modifierNode = marker;
continue;
}
modifierNode = this._createBinaryOperationNode(
modifierNode,
marker,
this._createViperSyntheticBarToken(attributeToken),
OperatorType.BitwiseOr
);
}
if (!modifierNode) {
return innerType;
}
return this._createBinaryOperationNode(
modifierNode,
innerType,
this._createViperSyntheticBarToken(attributeToken),
OperatorType.BitwiseOr
);
}
// Viper 扩展:合成零长度的 '|' 算子 token(源码中不存在,仅承载位置信息)。
private _createViperSyntheticBarToken(anchorToken: Token): OperatorToken {
return OperatorToken.create(
anchorToken.start + anchorToken.length,
0,
OperatorType.BitwiseOr,
/* comments */ undefined
);
}
// Viper 扩展:当前(可带 offset)是否为注解位置的 `__attribute__(` 前缀。
// `__attribute__` 是普通标识符(非关键字),后必须紧邻 '('。
private _isAtViperAttributePrefix(offset: number): boolean {
const nameToken = this._peekToken(offset);
if (nameToken.type !== TokenType.Identifier) {
return false;
}
if (this._fileContents!.slice(nameToken.start, nameToken.start + nameToken.length) !== '__attribute__') {
return false;
}
return this._peekToken(offset + 1).type === TokenType.OpenParenthesis;
}
// Viper 扩展:把 `__attribute__(...)` 的实参脱糖为 `t.attr.<name>` 标记。
// - `always_inline`(Name) → `t.attr.always_inline`
// - `section(".x")`(Call,callee 是 Name)→ `t.attr.section(".x")`
// - 已是 `t.attr.X` / `c.Attribute(...)` 形态 → 原样保留(允许混写)
// 与 TPC/TPV 的 includes/ast/parser.vp `_MakeTAttrMarker` 逐条对应;
// 成员名复用实参自身的 token(真实位置),故属性名上的跳转/悬停仍指向 t.vpi。
// 根 `t` 是合成的「隐式模块根」(见 _createViperImplicitModuleName),
// 不要求源码 `import t`。
private _makeViperAttrMarker(argExpr: ExpressionNode): ExpressionNode {
const attrRoot = MemberAccessNode.create(
this._createViperImplicitModuleName(argExpr.start, 't'),
NameNode.create(IdentifierToken.create(argExpr.start, 0, 'attr', /* comments */ undefined))
);
if (argExpr.nodeType === ParseNodeType.Name) {
return MemberAccessNode.create(attrRoot, NameNode.create(argExpr.d.token));
}
if (argExpr.nodeType === ParseNodeType.Call && argExpr.d.leftExpr.nodeType === ParseNodeType.Name) {
const inner = MemberAccessNode.create(attrRoot, NameNode.create(argExpr.d.leftExpr.d.token));
return CallNode.create(inner, argExpr.d.args, argExpr.d.trailingComma);
}
// 已是属性标记形态(t.attr.X / c.Attribute(...)):原样保留。
return argExpr;
}
// Viper 扩展:合成 zero-length 的「隐式模块根」名(`t` / `c`)。
// `__attribute__` 的脱糖节点里引用 `t` / `c`,但**不要求源码 import t / import c**
// —— 与编译器一致(TPC/TPV 的属性识别是 AST 级的,不看符号是否可见)。
// 该名在词法作用域里解析不到时,typeEvaluator 会回退到 builtins 作用域的同名
// 模块绑定(见 NameNode.d.isViperImplicitModule)。
private _createViperImplicitModuleName(start: number, name: string): NameNode {
const nameNode = NameNode.create(IdentifierToken.create(start, 0, name, /* comments */ undefined));
nameNode.d.isViperImplicitModule = true;
return nameNode;
}
// Viper 扩展:把 `@__attribute__(...)` 装饰器脱糖为
// `@c.Attribute(t.attr.a, t.attr.b)`——与 TPC/TPV 的
// includes/ast/parser.vp `_DesugarMagicAttribute` 逐字段同形,因此 class 与 def
// 共用同一形态;其它装饰器原样返回。
private _desugarViperMagicAttribute(expr: ExpressionNode): ExpressionNode {
if (this._parseOptions.dialect !== 'viper' || expr.nodeType !== ParseNodeType.Call) {
return expr;
}
if (!this._isViperMagicAttributeCallee(expr.d.leftExpr)) {
return expr;
}
const attributeCallee = MemberAccessNode.create(
this._createViperImplicitModuleName(expr.start, 'c'),
NameNode.create(IdentifierToken.create(expr.start, 0, 'Attribute', /* comments */ undefined))
);
const newArgs = expr.d.args.map((arg) => {
if (arg.d.argCategory !== ArgCategory.Simple) {
return arg;
}
return ArgumentNode.create(undefined, this._makeViperAttrMarker(arg.d.valueExpr), ArgCategory.Simple);
});
return CallNode.create(attributeCallee, newArgs, expr.d.trailingComma);
}
// Viper 扩展:装饰器被调用者是否为 `__attribute__`(Name 形态,或 `X.__attribute__` 尾段形态)。
private _isViperMagicAttributeCallee(node: ExpressionNode): boolean {
if (node.nodeType === ParseNodeType.Name) {
return node.d.value === '__attribute__';
}
if (node.nodeType === ParseNodeType.MemberAccess) {
return node.d.member.d.value === '__attribute__';
}
return false;
}
// Viper 扩展:判定注解位置 `const` / `volatile` / `__attribute__(` 之后是否跟类型。
private _isViperTypeAtomStartToken(token: Token): boolean {
return (
token.type === TokenType.Identifier ||
token.type === TokenType.OpenBracket ||
(token.type === TokenType.Keyword &&
(token as KeywordToken).keywordType === KeywordType.ViperTypeDescriptor)
);
}
// Viper 扩展:当前是否为值表达式中的 `volatile <调用>` 前缀。
// 仅 `volatile` 有值位置用法(`const` 没有);且下一 token 必须是表达式起点
// (标识符 / '('),否则不算(`volatile = 2`、`volatile: int` 等仍走原错误路径)。
private _isAtViperVolatileValuePrefix(): boolean {
if (this._parseOptions.dialect !== 'viper' || this._isParsingTypeAnnotation) {
return false;
}
const descriptorToken = this._peekToken();
if (
descriptorToken.type !== TokenType.Keyword ||
(descriptorToken as KeywordToken).keywordType !== KeywordType.ViperTypeDescriptor
) {
return false;
}
if (
this._fileContents!.slice(descriptorToken.start, descriptorToken.start + descriptorToken.length) !==
'volatile'
) {
return false;
}
const nextTokenType = this._peekToken(1).type;
return nextTokenType === TokenType.Identifier || nextTokenType === TokenType.OpenParenthesis;
}
// Viper 扩展:消费值位置的 `volatile` 前缀,返回被修饰的调用节点。
// 语义上等价于该调用本身(volatile 仅影响代码生成,不改变类型),故直接返回
// CallNode:`volatile f()` 的类型即 f 的返回类型、`volatile 结构体()` 即结构体实例。
// 非调用形态(如 `volatile x`)报语法错误但仍返回已解析的表达式以维持错误恢复。
private _parseViperVolatileValue(): ExpressionNode {
const volatileToken = this._getNextToken();
const innerExpression = this._parseAtomExpression();
if (innerExpression.nodeType !== ParseNodeType.Call) {
this._addSyntaxError(LocMessage.viperVolatileValueRequiresCall(), volatileToken);
}
return innerExpression;
}
// Viper 扩展:类型描述符前缀 `const T` / `volatile T`。
// 解析层脱糖为等价的联合类型注解 `CConst | T` / `CVolatile | T`,
// 与手写 `a: t.CConst | CInt` 的 AST 同形,从而完全复用既有 TypeSet
// 修饰符语义(类型求值、赋值兼容、运算跳过修饰符、hover、语义高亮均一致),
// 不引入第二套修饰机制。
//
// 硬关键字:const / volatile 是 Viper 保留字(tokenizer 生成
// KeywordType.ViperTypeDescriptor),语法上只允许「类型注解前缀」这一种用法,
// 其余位置(`const = 1`、`x.volatile`、注解中不跟类型等)由 parser 报语法错误。
//
// 脱糖目标 `CConst` / `CVolatile` 是 builtins stub 中的别名
// (见 typeshed-fallback/stdlib/builtins.vpi),故不要求源码 import t。
private _parseViperTypeDescriptorPrefix(): ExpressionNode | undefined {
if (this._parseOptions.dialect !== 'viper') {
return undefined;
}
const descriptorToken = this._peekToken();
if (
descriptorToken.type !== TokenType.Keyword ||
(descriptorToken as KeywordToken).keywordType !== KeywordType.ViperTypeDescriptor
) {
return undefined;
}
const descriptorName = this._fileContents!.slice(
descriptorToken.start,
descriptorToken.start + descriptorToken.length
);
const memberName = descriptorName === 'const' ? 'CConst' : 'CVolatile';
// 硬关键字:紧随其后必须是类型原子的起点(标识符 / '[' / 另一个描述符 /
// `__attribute__(` 属性前缀),否则报语法错误(如 `const = 1`、`a: const`)。
const nextToken = this._peekToken(1);
const isTypeAtomStart =
this._isViperTypeAtomStartToken(nextToken) || this._isAtViperAttributePrefix(1);
if (!isTypeAtomStart) {
this._addSyntaxError(
LocMessage.viperTypeDescriptorRequiresType().format({ name: descriptorName }),
descriptorToken
);
this._getNextToken();
return ErrorNode.create(descriptorToken, ErrorExpressionCategory.MissingExpression);
}
// 消费描述符关键字。
this._getNextToken();
// 递归解析描述符修饰的主体:支持 `const volatile int`、`const [4]int`。
// 期间禁止在表达式层就地消费指针后缀 `*`(置 _isParsingViperTypeDescriptorInner),
// 使 `const int*` 恒为 `(CConst | int)*` —— 指针后缀由 _parseTypeAnnotation
// 统一在最外层应用(指针指向被描述符修饰的类型)。
const wasDescriptorInner = this._isParsingViperTypeDescriptorInner;
this._isParsingViperTypeDescriptorInner = true;
const innerType = this._parseViperTypeAnnotationCore();
this._isParsingViperTypeDescriptorInner = wasDescriptorInner;
// 合成描述符名:零长度、位置落在描述符关键字起点,不产生语义高亮
// (semanticTokenProvider 跳过 length <= 0 的名字),也不参与位置命中。
const modifierNameNode = NameNode.create(
IdentifierToken.create(descriptorToken.start, 0, memberName, /* comments */ undefined)
);
modifierNameNode.d.isViperTypeDescriptor = true;
// 合成 '|' 算子 token:源码中不存在,位置紧随描述符关键字,零长度。
const operatorToken = OperatorToken.create(
descriptorToken.start + descriptorToken.length,
0,
OperatorType.BitwiseOr,
/* comments */ undefined
);
return BinaryOperationNode.create(modifierNameNode, innerType, operatorToken, OperatorType.BitwiseOr);
}
// Viper 扩展:判定当前 '*'/'**' 串是否为类型注解内的 C 指针后缀。
// 判据:星号串('*' 一层 / '**' 两层,可含换行)之后紧跟"无右操作数"位置,
// 即 ']' / ',' / ')' / ':' / '=' / 换行 / ';' / 文件结束。
// 因此 `t.CArray[X, N * 2]`、`a ** b` 这类'*'后有操作数的真实运算不会被误判。
private _isAtViperPointerTypeSuffix(): boolean {
let offset = 0;
let starCount = 0;
while (true) {
const token = this._peekToken(offset);
if (token.type === TokenType.Operator) {
const operatorType = (token as OperatorToken).operatorType;
if (operatorType === OperatorType.Multiply) {
starCount += 1;
offset += 1;
continue;
}
if (operatorType === OperatorType.Power) {
starCount += 2;
offset += 1;
continue;
}
}
if (token.type === TokenType.NewLine) {
offset += 1;
continue;
}
break;
}
if (starCount === 0) {
return false;
}
const nextToken = this._peekToken(offset);
switch (nextToken.type) {
case TokenType.CloseBracket:
case TokenType.Comma:
case TokenType.CloseParenthesis:
case TokenType.Colon:
case TokenType.Semicolon:
case TokenType.NewLine:
case TokenType.EndOfStream:
return true;
case TokenType.Operator:
return (nextToken as OperatorToken).operatorType === OperatorType.Assign;
}
return false;
}
// Viper 扩展:判定当前 '*'/'**' 串是否为 `T*(x)` 形式的 C 风格指针强转调用。
// 判据(三条同时成立,与 TPC ExprParser._AtPtrSuffixCall 同源):
// 1. base 是「类型原子」形态(Name / MemberAccess / Index);
// 2. base 与星串**紧贴**(源码字符相邻,无空白/换行);
// 3. 星串内部逐个紧贴,且其**紧贴**一个 '('。
// 为什么 '(' 不并入 _isAtViperPointerTypeSuffix 的「无右操作数位置」:`a * (b)` 是
// 合法乘法('(' 可作乘法右操作数的起点)。故叠加「前驱是类型原子 + 两侧紧贴」两条,
// 使 `a * (b + c)`、`f(x) * (y)`、`(i + 1) * (j)` 仍按乘法解析。
private _isAtViperPointerSuffixCastCall(baseExpr: ExpressionNode): boolean {
if (this._parseOptions.dialect !== 'viper') {
return false;
}
switch (baseExpr.nodeType) {
case ParseNodeType.Name:
case ParseNodeType.MemberAccess:
case ParseNodeType.Index:
break;
default:
return false;
}
// base 与星串是否紧贴:用已消费的最后一个 token 的结束位置做判据。
const prevToken = this._peekToken(-1);
let starEndOffset = prevToken.start + prevToken.length;
let offset = 0;
let starCount = 0;
while (true) {
const token = this._peekToken(offset);
const operatorType =
token.type === TokenType.Operator ? (token as OperatorToken).operatorType : undefined;
if (operatorType !== OperatorType.Multiply && operatorType !== OperatorType.Power) {
break;
}
if (token.start !== starEndOffset) {
// base 与星串之间、或星串内部有空白:非 C 风格强转写法。
return false;
}
starEndOffset = token.start + token.length;
starCount += 1;
offset += 1;
}
if (starCount === 0) {
return false;
}
const nextToken = this._peekToken(offset);
return nextToken.type === TokenType.OpenParenthesis && nextToken.start === starEndOffset;
}
// Viper 扩展:定长数组类型语法 `[SIZE]T`(等价 t.CArray[T, SIZE])与
// 不定长写法 `[]T`(等价 t.CArray[T],与 `t.CArray[T]` 单参数一致)。
// 生效位置:
// - 类型注解起点(变量/参数/返回值/类成员/type 别名);
// - 下标实参内,但仅当形如 `[SIZE]T` 时(见 _isAtViperSubscriptBracketArrayType),
// 使 `list[[4]int]` 可用,同时不影响 `t.Callable[[int, str], None]`;
// - `[SIZE]T` 的元素类型内(支持 `[2][3]T`、`list[[2][3]int]`)。
// 值表达式里的 `[SIZE]` 仍是 list 字面量。
private _parseViperBracketArrayTypeIfNeeded(): ExpressionNode {
if (
this._parseOptions.dialect === 'viper' &&
(!this._isParsingIndexTrailer || this._isParsingViperBracketArrayElement) &&
this._peekTokenType() === TokenType.OpenBracket
) {
return this._parseViperBracketArrayType();
}
return this._parseTestExpression(/* allowAssignmentExpression */ false);
}
// Viper 扩展:判断下标实参位置(如 `list[[4]int]`)是否是定长数组类型 `[SIZE]T`。
// 与 `t.Callable[[int, str], None]` / `t.Callable[[], None]` 的实参列表区别在于
// 匹配的 `]` 之后是否紧跟元素类型:`[4]int` 是数组,`[int, str]`、`[]` 不是。
// 仅做 token 前瞻,不解析、不产生诊断。
private _isAtViperSubscriptBracketArrayType(): boolean {
if (this._parseOptions.dialect !== 'viper' || this._peekToken(0).type !== TokenType.OpenBracket) {
return false;
}
// 跳过 [SIZE]:SIZE 内允许嵌套下标,按括号配对找匹配的 ']'。
let offset = 1;
let depth = 0;
while (true) {
const token = this._peekToken(offset);
if (token.type === TokenType.OpenBracket) {
depth += 1;
} else if (token.type === TokenType.CloseBracket) {
if (depth === 0) {
break;
}
depth -= 1;
} else if (token.type === TokenType.NewLine || token.type === TokenType.EndOfStream) {
return false;
}
offset += 1;
}
// ']' 之后必须是元素类型的起始(标识符,或嵌套数组的 '[')。
const elementStartToken = this._peekToken(offset + 1);
return elementStartToken.type === TokenType.Identifier || elementStartToken.type === TokenType.OpenBracket;
}
// Viper 扩展:解析 `[SIZE]T` / `[]T` 并脱糖为等价的 `t.CArray[T, SIZE]` /
// `t.CArray[T]` 下标表达式,使类型求值、语义高亮、类型显示完全复用既有
// t.CArray 机制(元素类型、索引返回 T、数组名退化为指针、字符数组降级
// str/bytes、单参数不定长等),不引入第二套语义。
// 元素类型递归解析,故支持 `[2][3]T`(等价 t.CArray[t.CArray[T, 3], 2])、
// `[4]T*`(指针后缀)、`[4]T | t.CPtr`(联合)等。
private _parseViperBracketArrayType(): ExpressionNode {
const openBracketToken = this._getNextToken();
assert(openBracketToken.type === TokenType.OpenBracket);
let sizeExpr: ExpressionNode | undefined;
if (this._peekTokenType() !== TokenType.CloseBracket) {
sizeExpr = this._parseTestExpression(/* allowAssignmentExpression */ false);
}
const closeBracketToken = this._peekToken();
if (!this._consumeTokenIfType(TokenType.CloseBracket)) {
this._addSyntaxError(LocMessage.expectedCloseBracket(), this._peekToken());
}
// 元素类型:递归走注解解析,天然支持嵌套 `[2][3]T` 与指针后缀 `[4]T*`。
// 期间允许在下标实参内继续出现 `[SIZE]T`(如 list[[2][3]int])。
const wasParsingBracketArrayElement = this._isParsingViperBracketArrayElement;
this._isParsingViperBracketArrayElement = true;
const elementType = this._parseTypeAnnotation();
this._isParsingViperBracketArrayElement = wasParsingBracketArrayElement;
// 合成 `t.CArray`:零长度、位置落在 '[' 上,因此不产生任何语义高亮
// (semanticTokenProvider 跳过 length <= 0 的名字)也不参与位置命中。
const syntheticTNameNode = NameNode.create(
IdentifierToken.create(openBracketToken.start, 0, 't', /* comments */ undefined)
);
syntheticTNameNode.d.isViperBracketArraySynthetic = true;
const cArrayNameNode = NameNode.create(
IdentifierToken.create(openBracketToken.start, 0, 'CArray', /* comments */ undefined)
);
const memberAccessNode = MemberAccessNode.create(syntheticTNameNode, cArrayNameNode);
// items 按**源码顺序**排列([长度] 在 '[' 内、元素类型在 ']' 之后),
// 以满足"子节点必须按位置递增且不重叠"的 AST 不变量;
// 类型求值在 getTypeArgs 中按语义顺序(元素类型在前)重排实参。
const items: ArgumentNode[] = [];
if (sizeExpr) {
items.push(ArgumentNode.create(/* startToken */ undefined, sizeExpr, ArgCategory.Simple));
}
items.push(ArgumentNode.create(/* startToken */ undefined, elementType, ArgCategory.Simple));
const indexNode = IndexNode.create(memberAccessNode, items, /* trailingComma */ false, closeBracketToken);
indexNode.d.isViperBracketArray = true;
// 让整个下标节点的范围覆盖 `[SIZE]T` 全文(含 ']' 之后的元素类型),
// 使 hover/定位落在元素类型内时仍能沿父链找到该数组类型节点。
extendRange(indexNode, elementType);
return indexNode;
}
// 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._markViperPointerTypeSuffix(
this._createUnaryOperationNode(starToken, result, OperatorType.Multiply)
);
} else if (nextOperator === OperatorType.Power) {
const starToken = this._getNextToken();
result = this._markViperPointerTypeSuffix(
this._createUnaryOperationNode(starToken, result, OperatorType.Multiply)
);
result = this._markViperPointerTypeSuffix(
this._createUnaryOperationNode(starToken, result, OperatorType.Multiply)
);
} else {
break;
}
}
return result;
}
// Viper 扩展:标记由 C 指针类型后缀 `T*` 生成的一元 '*' 节点(区别于前缀解引用 `*x`)。
// 供 `(T*)x` 强转解析(_parseAtom)区分「括号内的指针类型」与「括号内的解引用」。
private _markViperPointerTypeSuffix(node: ExpressionNode): ExpressionNode {
if (node.nodeType === ParseNodeType.UnaryOperation) {
(node as UnaryOperationNode).d.isViperPointerTypeSuffix = true;
}
return node;
}
// Viper 扩展:`(T)x` / `(T*)x` 强转解析。
// 当括号内是「类型原子」形态且紧随其后是操作数起点时,将其后的操作数作为
// 该强转的唯一实参,构造 CallNode 返回。
// 类型原子:
// - Name:`(Buf)v`;
// - MemberAccess:`(t.CInt)x`;
// - Subscript:`(list[X])v`;
// - 指针后缀一元 '*':`(T*)x`(由 _parseViperPointerTypeSuffix 标记)。
// 语义等价于已支持的 `T(x)` / `(T)(x)` 调用式强转通路:
// CallNode(func = 括号内类型表达式, args = [操作数])。
// 边界:`(a)*b`、`(a) * (b)` 仍是乘法('*' 不是操作数起点);
// `(T*)[i]` 仍是下标('[' 不是操作数起点);
// `(a)b` 由语法错误变为「调用 a(b)」——可接受的超集,
// 值为类型即强转,否则由语义层报「不可调用」。
private _tryParseViperPointerCast(possibleTupleNode: ExpressionNode): ExpressionNode | undefined {
if (this._parseOptions.dialect !== 'viper') {
return undefined;
}
if (!this._isViperCastTypeAtom(possibleTupleNode)) {
return undefined;
}
if (!this._isAtViperCastOperandStart()) {
return undefined;
}
// 按 C 的强转优先级解析操作数(一元表达式级),故 `(T*)a + b` = `((T*)a) + b`。
const operand = this._parseArithmeticFactor();
const argNode = ArgumentNode.create(/* startToken */ undefined, operand, ArgCategory.Simple);
return CallNode.create(possibleTupleNode, [argNode], /* trailingComma */ false);
}
// Viper 扩展:判定括号内的表达式是否为「类型原子」(可用作 C 强转的类型部分)。
// 仅接受 Name / MemberAccess / Subscript / 指针后缀一元 '*',
// 故 `(1+2)a`、`(*p)a`(前缀解引用)等不会被当作强转。
private _isViperCastTypeAtom(node: ExpressionNode): boolean {
switch (node.nodeType) {
case ParseNodeType.Name:
case ParseNodeType.MemberAccess:
case ParseNodeType.Index:
return true;
case ParseNodeType.UnaryOperation:
return !!(node as UnaryOperationNode).d.isViperPointerTypeSuffix;
default:
return false;
}
}
// Viper 扩展:判定当前位置是否为 `(T*)` 之后可作为强转操作数的表达式起点。
// 仅接受操作数起点(标识符 / 数字 / 字符串 / f-string / 字面量关键字 / 字典集合 / 反引号),
// 排除运算符与 '('、'[':后两者由 _parseAtomExpression 的 trailer 分别按函数调用、
// 下标处理(即 `(T*)(x)` 走既有调用通路,`(T*)[i]` 保持下标语义),不受本扩展影响。
private _isAtViperCastOperandStart(): boolean {
const nextToken = this._peekToken();
switch (nextToken.type) {
case TokenType.Identifier:
case TokenType.Number:
case TokenType.String:
case TokenType.FStringStart:
case TokenType.OpenCurlyBrace:
case TokenType.Backtick:
return true;
case TokenType.Keyword: {
const keywordToken = nextToken as KeywordToken;
return (
keywordToken.keywordType === KeywordType.False ||
keywordToken.keywordType === KeywordType.True ||
keywordToken.keywordType === KeywordType.None
);
}
default:
return false;
}
}
private _reportStringTokenErrors(
stringToken: StringToken | FStringStartToken,
unescapedResult?: StringTokenUtils.UnescapedString
) {
if (stringToken.flags & StringTokenFlags.Unterminated) {
this._addSyntaxError(LocMessage.stringUnterminated(), stringToken);
}
if (unescapedResult?.nonAsciiInBytes) {
this._addSyntaxError(LocMessage.stringNonAsciiBytes(), stringToken);
}
if (stringToken.flags & StringTokenFlags.Format) {
if (PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_6)) {
this._addSyntaxError(LocMessage.formatStringIllegal(), stringToken);
}
if (stringToken.flags & StringTokenFlags.Bytes) {
this._addSyntaxError(LocMessage.formatStringBytes(), stringToken);
}
if (stringToken.flags & StringTokenFlags.Unicode) {
this._addSyntaxError(LocMessage.formatStringUnicode(), stringToken);
}
if (stringToken.flags & StringTokenFlags.Template) {
this._addSyntaxError(LocMessage.formatStringTemplate(), stringToken);
}
}
if (stringToken.flags & StringTokenFlags.Template) {
if (PythonVersion.isLessThan(this._getLanguageVersion(), pythonVersion3_14)) {
this._addSyntaxError(LocMessage.templateStringIllegal(), stringToken);
}
if (stringToken.flags & StringTokenFlags.Bytes) {
this._addSyntaxError(LocMessage.templateStringBytes(), stringToken);
}
if (stringToken.flags & StringTokenFlags.Unicode) {
this._addSyntaxError(LocMessage.templateStringUnicode(), stringToken);
}
}
}
private _makeStringNode(stringToken: StringToken): StringNode {
const unescapedResult = StringTokenUtils.getUnescapedString(stringToken);
this._reportStringTokenErrors(stringToken, unescapedResult);
return StringNode.create(stringToken, unescapedResult.value);
}
private _getTypeAnnotationCommentText(): StringToken | undefined {
if (this._tokenIndex === 0) {
return undefined;
}
const curToken = this._tokenizerOutput!.tokens.getItemAt(this._tokenIndex - 1);
const nextToken = this._tokenizerOutput!.tokens.getItemAt(this._tokenIndex);
if (curToken.start + curToken.length === nextToken.start) {
return undefined;
}
const interTokenContents = this._fileContents!.slice(curToken.start + curToken.length, nextToken.start);
const match = interTokenContents.match(commentRegEx);
if (!match) {
return undefined;
}
// Synthesize a string token and StringNode.
const typeString = match[2];
// Ignore all "ignore" comments. Include "[" in the regular
// expression because mypy supports ignore comments of the
// form ignore[errorCode, ...]. We'll treat these as regular
// ignore statements (as though no errorCodes were included).
if (typeString.trim().match(ignoreCommentRegEx)) {
return undefined;
}
const tokenOffset = curToken.start + curToken.length + match[1].length;
return StringToken.create(
tokenOffset,
typeString.length,
StringTokenFlags.None,
typeString,
0,
/* comments */ undefined
);
}
private _parseVariableTypeAnnotationComment(): ExpressionNode | undefined {
const stringToken = this._getTypeAnnotationCommentText();
if (!stringToken) {
return undefined;
}
const stringNode = this._makeStringNode(stringToken);
const stringListNode = StringListNode.create([stringNode]);
const parser = new Parser();
const parseResults = parser.parseTextExpression(
this._fileContents!,
stringToken.start,
stringToken.length,
this._parseOptions,
ParseTextMode.VariableAnnotation,
/* initialParenDepth */ undefined,
this._typingSymbolAliases
);
parseResults.diagnostics.forEach((diag) => {
this._addSyntaxError(diag.message, stringListNode);
});
if (!parseResults.parseTree) {
return undefined;
}
return parseResults.parseTree;
}
private _parseFunctionTypeAnnotationComment(stringToken: StringToken, functionNode: FunctionNode): void {
const stringNode = this._makeStringNode(stringToken);
const stringListNode = StringListNode.create([stringNode]);
const parser = new Parser();
const parseResults = parser.parseTextExpression(
this._fileContents!,
stringToken.start,
stringToken.length,
this._parseOptions,
ParseTextMode.FunctionAnnotation,
/* initialParenDepth */ undefined,
this._typingSymbolAliases
);
parseResults.diagnostics.forEach((diag) => {
this._addSyntaxError(diag.message, stringListNode);
});
if (!parseResults.parseTree) {
return;
}
const functionAnnotation = parseResults.parseTree;
functionNode.d.funcAnnotationComment = functionAnnotation;
functionAnnotation.parent = functionNode;
extendRange(functionNode, functionAnnotation);
}
private _parseFStringReplacementField(
fieldExpressions: ExpressionNode[],
middleTokens: FStringMiddleToken[],
formatExpressions: ExpressionNode[],
nestingDepth = 0
): boolean {
let nextToken = this._getNextToken();
// The caller should have already confirmed that the next token is an open brace.
assert(nextToken.type === TokenType.OpenCurlyBrace);
// Consume the expression.
const expr =
this._tryParseYieldExpression() ??
this._parseTestOrStarListAsExpression(
/* allowAssignmentExpression */ true,
/* allowMultipleUnpack */ true,
ErrorExpressionCategory.MissingExpression,
() => LocMessage.expectedExpr()
);
fieldExpressions.push(expr);
if (expr.nodeType === ParseNodeType.Error) {
return false;
}
// Consume an optional "=" token after the expression.
nextToken = this._peekToken();
if (
nextToken.type === TokenType.Operator &&
(nextToken as OperatorToken).operatorType === OperatorType.Assign
) {
// This feature requires Python 3.8 or newer.
if (PythonVersion.isLessThan(this._parseOptions.pythonVersion, pythonVersion3_8)) {
this._addSyntaxError(LocMessage.formatStringDebuggingIllegal(), nextToken);
}
this._getNextToken();
nextToken = this._peekToken();
}
// Consume an optional !r, !s, or !a token.
if (nextToken.type === TokenType.ExclamationMark) {
this._getNextToken();
nextToken = this._peekToken();
if (nextToken.type !== TokenType.Identifier) {
this._addSyntaxError(LocMessage.formatStringExpectedConversion(), nextToken);
} else {
this._getNextToken();
nextToken = this._peekToken();
}
}
if (nextToken.type === TokenType.Colon) {
this._getNextToken();
this._parseFStringFormatString(fieldExpressions, middleTokens, formatExpressions, nestingDepth);
nextToken = this._peekToken();
}
if (nextToken.type !== TokenType.CloseCurlyBrace) {
this._addSyntaxError(LocMessage.formatStringUnterminated(), nextToken);
return false;
} else {
this._getNextToken();
}
// Indicate success.
return true;
}
private _parseFStringFormatString(
fieldExpressions: ExpressionNode[],
middleTokens: FStringMiddleToken[],
formatExpressions: ExpressionNode[],
nestingDepth: number
) {
while (true) {
const nextToken = this._peekToken();
if (nextToken.type === TokenType.CloseCurlyBrace || nextToken.type === TokenType.FStringEnd) {
break;
}
if (nextToken.type === TokenType.FStringMiddle) {
this._getNextToken();
continue;
}
if (nextToken.type === TokenType.OpenCurlyBrace) {
// The Python interpreter reports an error at the point where the
// nesting level exceeds 1. Don't report the error again for deeper nestings.
if (nestingDepth === 2) {
this._addSyntaxError(LocMessage.formatStringNestedFormatSpecifier(), nextToken);
}
this._parseFStringReplacementField(fieldExpressions, middleTokens, formatExpressions, nestingDepth + 1);
continue;
}
break;
}
}
private _parseFormatString(startToken: FStringStartToken): FormatStringNode {
const middleTokens: FStringMiddleToken[] = [];
const fieldExpressions: ExpressionNode[] = [];
const formatExpressions: ExpressionNode[] = [];
let endToken: FStringEndToken | undefined = undefined;
// Consume middle tokens and expressions until we hit a "{" or "}" token.
while (true) {
const nextToken = this._peekToken();
if (nextToken.type === TokenType.FStringEnd) {
endToken = nextToken as FStringEndToken;
if ((endToken.flags & StringTokenFlags.Unterminated) !== 0) {
this._addSyntaxError(LocMessage.stringUnterminated(), startToken);
}
this._getNextToken();
break;
}
if (nextToken.type === TokenType.FStringMiddle) {
middleTokens.push(nextToken as FStringMiddleToken);
this._getNextToken();
continue;
}
if (nextToken.type === TokenType.OpenCurlyBrace) {
if (!this._parseFStringReplacementField(fieldExpressions, middleTokens, formatExpressions)) {
// An error was reported. Try to recover the parse.
if (this._consumeTokensUntilType([TokenType.FStringEnd, TokenType.NewLine])) {
if (this._peekToken().type === TokenType.FStringEnd) {
this._getNextToken();
}
}
break;
}
continue;
}
// We've hit an error. Try to recover as gracefully as possible.
if (nextToken.type !== TokenType.NewLine) {
// Consume tokens until we find the end.
if (this._consumeTokensUntilType([TokenType.FStringEnd])) {
this._getNextToken();
}
}
this._addSyntaxError(
nextToken.type === TokenType.CloseCurlyBrace
? LocMessage.formatStringBrace()
: LocMessage.stringUnterminated(),
nextToken
);
break;
}
this._reportStringTokenErrors(startToken);
return FormatStringNode.create(startToken, endToken, middleTokens, fieldExpressions, formatExpressions);
}
private _createBinaryOperationNode(
leftExpression: ExpressionNode,
rightExpression: ExpressionNode,
operatorToken: Token,
operator: OperatorType
) {
const binaryNode = BinaryOperationNode.create(leftExpression, rightExpression, operatorToken, operator);
// Determine if we're exceeding the max parse depth. If so, replace
// the subnode with an error node. Otherwise we risk crashing in the binder
// or type evaluator.
const leftMaxDepth = this._maxChildDepthMap.get(leftExpression.id) ?? 0;
const rightMaxDepth = this._maxChildDepthMap.get(rightExpression.id) ?? 0;
if (leftMaxDepth >= maxChildNodeDepth || rightMaxDepth >= maxChildNodeDepth) {
this._addSyntaxError(LocMessage.maxParseDepthExceeded(), binaryNode);
return ErrorNode.create(binaryNode, ErrorExpressionCategory.MaxDepthExceeded);
}
this._maxChildDepthMap.set(binaryNode.id, Math.max(leftMaxDepth, rightMaxDepth) + 1);
return binaryNode;
}
private _createUnaryOperationNode(operatorToken: Token, expression: ExpressionNode, operator: OperatorType) {
const unaryNode = UnaryOperationNode.create(operatorToken, expression, operator);
// Determine if we're exceeding the max parse depth. If so, replace
// the subnode with an error node. Otherwise we risk crashing in the binder
// or type evaluator.
const maxDepth = this._maxChildDepthMap.get(expression.id) ?? 0;
if (maxDepth >= maxChildNodeDepth) {
this._addSyntaxError(LocMessage.maxParseDepthExceeded(), unaryNode);
return ErrorNode.create(unaryNode, ErrorExpressionCategory.MaxDepthExceeded);
}
this._maxChildDepthMap.set(unaryNode.id, maxDepth + 1);
return unaryNode;
}
private _parseStringList(): StringListNode {
const stringList: (StringNode | FormatStringNode)[] = [];
while (true) {
const nextToken = this._peekToken();
if (nextToken.type === TokenType.String) {
stringList.push(this._makeStringNode(this._getNextToken() as StringToken));
} else if (nextToken.type === TokenType.FStringStart) {
stringList.push(this._parseFormatString(this._getNextToken() as FStringStartToken));
} else {
break;
}
}
const stringNode = StringListNode.create(stringList);
// If we're parsing a type annotation, parse the contents of the string.
if (this._isParsingTypeAnnotation) {
// Don't allow multiple strings because we have no way of reporting
// parse errors that span strings.
if (stringNode.d.strings.length > 1) {
if (this._isParsingQuotedText) {
this._addSyntaxError(LocMessage.annotationSpansStrings(), stringNode);
}
} else if (stringNode.d.strings[0].nodeType === ParseNodeType.FormatString) {
if (this._isParsingQuotedText) {
this._addSyntaxError(LocMessage.annotationFormatString(), stringNode);
}
} else {
const stringToken = stringNode.d.strings[0].d.token;
const stringValue = StringTokenUtils.getUnescapedString(
stringNode.d.strings[0].d.token,
/* elideCrlf */ false
);
const unescapedString = stringValue.value;
const tokenOffset = stringToken.start;
const prefixLength = stringToken.prefixLength + stringToken.quoteMarkLength;
// Don't allow escape characters because we have no way of mapping
// error ranges back to the escaped text.
if (unescapedString.length !== stringToken.length - prefixLength - stringToken.quoteMarkLength) {
if (this._isParsingQuotedText) {
this._addSyntaxError(LocMessage.annotationStringEscape(), stringNode);
}
} else if (
(stringToken.flags &
(StringTokenFlags.Raw |
StringTokenFlags.Bytes |
StringTokenFlags.Format |
StringTokenFlags.Template)) ===
0
) {
const parser = new Parser();
const parseResults = parser.parseTextExpression(
this._fileContents!,
tokenOffset + prefixLength,
unescapedString.length,
this._parseOptions,
ParseTextMode.VariableAnnotation,
(stringNode.d.strings[0].d.token.flags & StringTokenFlags.Triplicate) !== 0 ? 1 : 0,
this._typingSymbolAliases
);
if (
parseResults.diagnostics.length === 0 ||
this._parseOptions.reportErrorsForParsedStringContents
) {
parseResults.diagnostics.forEach((diag) => {
this._addSyntaxError(diag.message, stringNode);
});
if (parseResults.parseTree) {
stringNode.d.annotation = parseResults.parseTree;
stringNode.d.annotation.parent = stringNode;
}
}
}
}
}
return stringNode;
}
// Python 3.8 added support for star (unpack) expressions in tuples
// following a return or yield statement in cases where the tuple
// wasn't surrounded in parentheses.
private _reportConditionalErrorForStarTupleElement(
possibleTupleExpr: ExpressionNode,
pythonVersion = pythonVersion3_8
) {
if (possibleTupleExpr.nodeType !== ParseNodeType.Tuple) {
return;
}
if (possibleTupleExpr.d.hasParens) {
return;
}
if (PythonVersion.isGreaterOrEqualTo(this._parseOptions.pythonVersion, pythonVersion)) {
return;
}
for (const expr of possibleTupleExpr.d.items) {
if (expr.nodeType === ParseNodeType.Unpack) {
this._addSyntaxError(LocMessage.unpackTuplesIllegal(), expr);
return;
}
}
}
// Peeks at the next token and returns true if it can never
// represent the start of an expression.
private _isNextTokenNeverExpression(): boolean {
const nextToken = this._peekToken();
switch (nextToken.type) {
case TokenType.Keyword: {
switch (this._peekKeywordType()) {
case KeywordType.For:
case KeywordType.In:
case KeywordType.If:
return true;
}
break;
}
case TokenType.Operator: {
switch (this._peekOperatorType()) {
case OperatorType.AddEqual:
case OperatorType.SubtractEqual:
case OperatorType.MultiplyEqual:
case OperatorType.DivideEqual:
case OperatorType.ModEqual:
case OperatorType.BitwiseAndEqual:
case OperatorType.BitwiseOrEqual:
case OperatorType.BitwiseXorEqual:
case OperatorType.LeftShiftEqual:
case OperatorType.RightShiftEqual:
case OperatorType.PowerEqual:
case OperatorType.FloorDivideEqual:
case OperatorType.Assign:
return true;
}
break;
}
case TokenType.Indent:
case TokenType.Dedent:
case TokenType.NewLine:
case TokenType.EndOfStream:
case TokenType.Semicolon:
case TokenType.CloseParenthesis:
case TokenType.CloseBracket:
case TokenType.CloseCurlyBrace:
case TokenType.Comma:
case TokenType.Colon:
case TokenType.ExclamationMark:
case TokenType.FStringMiddle:
case TokenType.FStringEnd:
return true;
}
return false;
}
private _disallowAssignmentExpression(callback: () => void) {
const wasAllowed = this._assignmentExpressionsAllowed;
this._assignmentExpressionsAllowed = false;
callback();
this._assignmentExpressionsAllowed = wasAllowed;
}
private _getNextToken(): Token {
const token = this._tokens!.getItemAt(this._tokenIndex);
if (!this._atEof()) {
this._tokenIndex++;
}
return token;
}
private _atEof(): boolean {
// Are we pointing at the last token in the stream (which is
// assumed to be an end-of-stream token)?
return this._tokenIndex >= this._tokenCount - 1;
}
private _peekToken(count = 0): Token {
const targetIndex = this._tokenIndex + count;
if (targetIndex < 0) {
return this._tokens!.getItemAt(0);
}
if (targetIndex >= this._tokenCount) {
return this._tokens!.getItemAt(this._tokenCount - 1);
}
return this._tokens!.getItemAt(targetIndex);
}
private _peekTokenType(): TokenType {
return this._peekToken().type;
}
private _peekKeywordType(): KeywordType | undefined {
const nextToken = this._peekToken();
if (nextToken.type !== TokenType.Keyword) {
return undefined;
}
return (nextToken as KeywordToken).keywordType;
}
private _peekOperatorType(): OperatorType | undefined {
const nextToken = this._peekToken();
if (nextToken.type !== TokenType.Operator) {
return undefined;
}
return (nextToken as OperatorToken).operatorType;
}
private _getTokenIfIdentifier(): IdentifierToken | undefined {
const nextToken = this._peekToken();
if (nextToken.type === TokenType.Identifier) {
return this._getNextToken() as IdentifierToken;
}
// If the next token is invalid, treat it as an identifier.
if (nextToken.type === TokenType.Invalid) {
this._getNextToken();
this._addSyntaxError(LocMessage.invalidIdentifierChar(), nextToken);
return IdentifierToken.create(nextToken.start, nextToken.length, '', nextToken.comments);
}
// If this is a "soft keyword", it can be converted into an identifier.
if (nextToken.type === TokenType.Keyword) {
const keywordToken = nextToken as KeywordToken;
if (KeywordToken.isSoftKeyword(keywordToken)) {
const keywordText = this._fileContents!.substr(nextToken.start, nextToken.length);
this._getNextToken();
return IdentifierToken.create(nextToken.start, nextToken.length, keywordText, nextToken.comments);
}
}
return undefined;
}
// Consumes tokens until the next one in the stream is
// either a specified terminator or the end-of-stream
// token.
private _consumeTokensUntilType(terminators: TokenType[]): boolean {
while (true) {
const token = this._peekToken();
if (terminators.some((term) => term === token.type)) {
return true;
}
if (token.type === TokenType.EndOfStream) {
return false;
}
this._getNextToken();
}
}
private _getTokenIfType(tokenType: TokenType): Token | undefined {
if (this._peekTokenType() === tokenType) {
return this._getNextToken();
}
return undefined;
}
private _consumeTokenIfType(tokenType: TokenType): boolean {
return !!this._getTokenIfType(tokenType);
}
private _consumeTokenIfKeyword(keywordType: KeywordType): boolean {
if (this._peekKeywordType() === keywordType) {
this._getNextToken();
return true;
}
return false;
}
private _consumeTokenIfOperator(operatorType: OperatorType): boolean {
if (this._peekOperatorType() === operatorType) {
this._getNextToken();
return true;
}
return false;
}
private _getKeywordToken(keywordType: KeywordType): KeywordToken {
const keywordToken = this._getNextToken() as KeywordToken;
assert(keywordToken.type === TokenType.Keyword);
assert(keywordToken.keywordType === keywordType);
return keywordToken;
}
private _getLanguageVersion() {
return this._parseOptions.pythonVersion;
}
private _suppressErrors(callback: () => void) {
const errorsWereSuppressed = this._areErrorsSuppressed;
try {
this._areErrorsSuppressed = true;
callback();
} finally {
this._areErrorsSuppressed = errorsWereSuppressed;
}
}
private _addSyntaxError(message: string, range: TextRange) {
assert(range !== undefined);
if (!this._areErrorsSuppressed) {
this._diagSink.addError(
message,
convertOffsetsToRange(range.start, range.start + range.length, this._tokenizerOutput!.lines)
);
}
}
}