diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 415b53fbb..b7cb4abc7 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -14775,6 +14775,16 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions } else { type = cloneBuiltinClassWithLiteral(node, classType, 'str', value); } + + itemExpr.strings.forEach((stringNode) => { + if ((stringNode.token.flags & StringTokenFlags.NamedUnicodeEscape) !== 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.literalNamedUnicodeEscape(), + stringNode + ); + } + }); } else if (itemExpr.nodeType === ParseNodeType.Number) { if (!itemExpr.isImaginary && itemExpr.isInteger) { type = cloneBuiltinClassWithLiteral(node, classType, 'int', itemExpr.value); diff --git a/packages/pyright-internal/src/localization/localize.ts b/packages/pyright-internal/src/localization/localize.ts index dfa413aaa..7da0dbc71 100644 --- a/packages/pyright-internal/src/localization/localize.ts +++ b/packages/pyright-internal/src/localization/localize.ts @@ -601,6 +601,7 @@ export namespace Localizer { export const listAssignmentMismatch = () => new ParameterizedString<{ type: string }>(getRawString('Diagnostic.listAssignmentMismatch')); export const listInAnnotation = () => getRawString('Diagnostic.listInAnnotation'); + export const literalNamedUnicodeEscape = () => getRawString('Diagnostic.literalNamedUnicodeEscape'); export const literalUnsupportedType = () => getRawString('Diagnostic.literalUnsupportedType'); export const literalEmptyArgs = () => getRawString('Diagnostic.literalEmptyArgs'); export const literalNotAllowed = () => getRawString('Diagnostic.literalNotAllowed'); diff --git a/packages/pyright-internal/src/localization/package.nls.en-us.json b/packages/pyright-internal/src/localization/package.nls.en-us.json index d1bebc61b..509428d2d 100644 --- a/packages/pyright-internal/src/localization/package.nls.en-us.json +++ b/packages/pyright-internal/src/localization/package.nls.en-us.json @@ -263,6 +263,7 @@ "listAssignmentMismatch": "Expression with type \"{type}\" cannot be assigned to target list", "listInAnnotation": "List expression not allowed in type annotation", "literalUnsupportedType": "Type arguments for \"Literal\" must be None, a literal value (int, bool, str, or bytes), or an enum value", + "literalNamedUnicodeEscape": "Named unicode escape sequences are not supported in \"Literal\" string annotations", "literalEmptyArgs": "Expected one or more type arguments after \"Literal\"", "literalNotAllowed": "\"Literal\" cannot be used in this context without a type argument", "literalNotCallable": "Literal type cannot be instantiated", diff --git a/packages/pyright-internal/src/parser/tokenizer.ts b/packages/pyright-internal/src/parser/tokenizer.ts index cb89a9323..323df0ed5 100644 --- a/packages/pyright-internal/src/parser/tokenizer.ts +++ b/packages/pyright-internal/src/parser/tokenizer.ts @@ -1593,6 +1593,7 @@ export class Tokenizer { this._cs.getCurrentChar() === Char.N && this._cs.nextChar === Char.OpenBrace ) { + flags |= StringTokenFlags.NamedUnicodeEscape; isInNamedUnicodeEscape = true; } else { // If this is an f-string, the only escapes that are allowed is for diff --git a/packages/pyright-internal/src/parser/tokenizerTypes.ts b/packages/pyright-internal/src/parser/tokenizerTypes.ts index a7f6ee712..d2060c5f0 100644 --- a/packages/pyright-internal/src/parser/tokenizerTypes.ts +++ b/packages/pyright-internal/src/parser/tokenizerTypes.ts @@ -170,6 +170,7 @@ export const enum StringTokenFlags { // Other conditions ReplacementFieldStart = 1 << 7, ReplacementFieldEnd = 1 << 8, + NamedUnicodeEscape = 1 << 9, // Error conditions Unterminated = 1 << 16, diff --git a/packages/pyright-internal/src/tests/tokenizer.test.ts b/packages/pyright-internal/src/tests/tokenizer.test.ts index 7d66d195b..1c1cfe528 100644 --- a/packages/pyright-internal/src/tests/tokenizer.test.ts +++ b/packages/pyright-internal/src/tests/tokenizer.test.ts @@ -1166,7 +1166,7 @@ test('Strings: good name escapes', () => { const stringToken0 = results.tokens.getItemAt(0) as StringToken; const unescapedValue0 = StringTokenUtils.getUnescapedString(stringToken0); assert.equal(stringToken0.type, TokenType.String); - assert.equal(stringToken0.flags, StringTokenFlags.DoubleQuote); + assert.equal(stringToken0.flags, StringTokenFlags.DoubleQuote | StringTokenFlags.NamedUnicodeEscape); assert.equal(stringToken0.length, 23); assert.equal(stringToken0.escapedValue, '\\N{caret escape blah}'); assert.equal(unescapedValue0.value, '-'); @@ -1174,7 +1174,7 @@ test('Strings: good name escapes', () => { const stringToken1 = results.tokens.getItemAt(1) as StringToken; const unescapedValue1 = StringTokenUtils.getUnescapedString(stringToken1); assert.equal(stringToken1.type, TokenType.String); - assert.equal(stringToken1.flags, StringTokenFlags.DoubleQuote); + assert.equal(stringToken1.flags, StringTokenFlags.DoubleQuote | StringTokenFlags.NamedUnicodeEscape); assert.equal(stringToken1.length, 10); assert.equal(stringToken1.escapedValue, 'a\\N{A9}a'); assert.equal(unescapedValue1.value, 'a-a'); @@ -1188,7 +1188,7 @@ test('Strings: bad name escapes', () => { const stringToken0 = results.tokens.getItemAt(0) as StringToken; const unescapedValue0 = StringTokenUtils.getUnescapedString(stringToken0); assert.equal(stringToken0.type, TokenType.String); - assert.equal(stringToken0.flags, StringTokenFlags.DoubleQuote); + assert.equal(stringToken0.flags, StringTokenFlags.DoubleQuote | StringTokenFlags.NamedUnicodeEscape); assert.equal(unescapedValue0.unescapeErrors.length, 1); assert.equal(stringToken0.length, 10); assert.equal(stringToken0.escapedValue, '\\N{caret'); @@ -1197,7 +1197,7 @@ test('Strings: bad name escapes', () => { const stringToken1 = results.tokens.getItemAt(1) as StringToken; const unescapedValue1 = StringTokenUtils.getUnescapedString(stringToken1); assert.equal(stringToken1.type, TokenType.String); - assert.equal(stringToken1.flags, StringTokenFlags.DoubleQuote); + assert.equal(stringToken1.flags, StringTokenFlags.DoubleQuote | StringTokenFlags.NamedUnicodeEscape); assert.equal(unescapedValue1.unescapeErrors.length, 1); assert.equal(stringToken1.length, 9); assert.equal(stringToken1.escapedValue, '\\N{.A9}');