Fixed a few bugs in f-string parsing and added minimal support for f-string format directives.

This commit is contained in:
Eric Traut 2019-06-18 15:42:14 -06:00
parent 38a038c1ae
commit d0a7771090
4 changed files with 40 additions and 13 deletions

View File

@ -18,14 +18,14 @@ import { PythonVersion } from '../common/pythonVersion';
import { TextRange } from '../common/textRange';
import { AssertNode, AssignmentNode, AugmentedAssignmentExpressionNode,
BinaryExpressionNode, BreakNode, CallExpressionNode, ClassNode, ConstantNode,
DecoratorNode, DelNode, ErrorExpressionNode, ExceptNode, ExpressionNode, ForNode,
FunctionNode, IfNode, ImportAsNode, ImportFromNode, IndexExpressionNode,
LambdaNode, ListComprehensionForNode, ListComprehensionNode, ListNode,
MemberAccessExpressionNode, ModuleNode, NameNode, ParameterCategory, ParseNode,
RaiseNode, ReturnNode, SliceExpressionNode, StringListNode, SuiteNode,
TernaryExpressionNode, TryNode, TupleExpressionNode, TypeAnnotationExpressionNode,
UnaryExpressionNode, UnpackExpressionNode, WhileNode, WithNode, YieldExpressionNode,
YieldFromExpressionNode } from '../parser/parseNodes';
DecoratorNode, DelNode, ErrorExpressionNode, ExceptNode, ExpressionNode, FormatStringNode,
ForNode, FunctionNode, IfNode, ImportAsNode, ImportFromNode,
IndexExpressionNode, LambdaNode, ListComprehensionForNode, ListComprehensionNode,
ListNode, MemberAccessExpressionNode, ModuleNode, NameNode, ParameterCategory,
ParseNode, RaiseNode, ReturnNode, SliceExpressionNode, StringListNode,
SuiteNode, TernaryExpressionNode, TryNode, TupleExpressionNode,
TypeAnnotationExpressionNode, UnaryExpressionNode, UnpackExpressionNode, WhileNode,
WithNode, YieldExpressionNode, YieldFromExpressionNode } from '../parser/parseNodes';
import { KeywordType } from '../parser/tokenizerTypes';
import { ScopeUtils } from '../scopeUtils';
import { AnalyzerFileInfo } from './analyzerFileInfo';
@ -1085,6 +1085,14 @@ export class TypeAnalyzer extends ParseTreeWalker {
return true;
}
visitFormatString(node: FormatStringNode): boolean {
node.expressions.forEach(formatExpr => {
this._getTypeOfExpression(formatExpr.expression, true, true);
});
return true;
}
visitName(node: NameNode) {
const nameValue = node.nameToken.value;
const symbolInScope = this._currentScope.lookUpSymbolRecursive(nameValue);

View File

@ -789,7 +789,7 @@ export class FormatStringExpression {
}
export class FormatStringNode extends ExpressionNode {
readonly nodeType = ParseNodeType.String;
readonly nodeType = ParseNodeType.FormatString;
token: StringToken;
value: string;
hasUnescapeErrors: boolean;

View File

@ -2339,10 +2339,23 @@ export class Parser {
for (let segment of unescapedResult.formatStringSegments) {
if (segment.isExpression) {
let parser = new Parser();
let parseResults = parser.parseTextExpression(this._fileContents!,
stringToken.start + stringToken.prefixLength + stringToken.quoteMarkLength + segment.offset,
segment.length, this._parseOptions);
const parser = new Parser();
// Determine if we need to truncate the expression because it
// contains formatting directives that start with a ! or :.
let segmentExprLength = segment.length;
const bangIndex = segment.value.search(/\![^=]/);
if (bangIndex >= 0) {
segmentExprLength = Math.min(segmentExprLength, bangIndex);
}
const colonIndex = segment.value.search(/\:/);
if (colonIndex >= 0) {
segmentExprLength = Math.min(segmentExprLength, colonIndex);
}
const parseResults = parser.parseTextExpression(this._fileContents!,
stringToken.start + stringToken.prefixLength + stringToken.quoteMarkLength +
segment.offset, segmentExprLength, this._parseOptions);
parseResults.diagnostics.forEach(diag => {
const textRangeStart = (diag.range ?

View File

@ -16,3 +16,9 @@ c = f"hello { 1 "
# Test f-string with double braces.
d = f"hello {{{1}}}"
# Test f-string with formatting directives.
e = f"hello { 2 != 3 !r:2 }"
# Test f-string with formatting directives.
f = f"hello { 2 != 3 :3 }"