diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 16b3821afb27..19a4e8e16de6 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -1772,6 +1772,22 @@ export function createTypeEvaluator( const isBytesNode = (node: StringNode | FormatStringNode) => (node.d.token.flags & StringTokenFlags.Bytes) !== 0; + const isTemplateNode = (node: StringNode | FormatStringNode) => + (node.d.token.flags & StringTokenFlags.Template) !== 0; + + // Check for mixing of t-string literals with str, bytes, or f-string + // literals. CPython reports this as a SyntaxError (PEP 750). + const firstTemplateIndex = node.d.strings.findIndex(isTemplateNode); + const firstNonTemplateIndex = node.d.strings.findIndex((str) => !isTemplateNode(str)); + if (firstTemplateIndex >= 0 && firstNonTemplateIndex >= 0) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.mixingTemplateAndStr(), + node.d.strings[Math.max(firstTemplateIndex, firstNonTemplateIndex)] + ); + + return { type: UnknownType.create() }; + } // Check for mixing of bytes and str, which is not allowed. const firstStrIndex = node.d.strings.findIndex((str) => !isBytesNode(str)); diff --git a/packages/pyright-internal/src/localization/localize.ts b/packages/pyright-internal/src/localization/localize.ts index 95ab0685b8b6..683a9612171c 100644 --- a/packages/pyright-internal/src/localization/localize.ts +++ b/packages/pyright-internal/src/localization/localize.ts @@ -683,6 +683,7 @@ export namespace Localizer { export const missingSuperCall = () => new ParameterizedString<{ methodName: string }>(getRawString('Diagnostic.missingSuperCall')); export const mixingBytesAndStr = () => getRawString('Diagnostic.mixingBytesAndStr'); + export const mixingTemplateAndStr = () => getRawString('Diagnostic.mixingTemplateAndStr'); export const moduleAsType = () => getRawString('Diagnostic.moduleAsType'); export const moduleNotCallable = () => getRawString('Diagnostic.moduleNotCallable'); export const moduleUnknownMember = () => 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 e65ef2a23ed5..5fb3edcdaeaf 100644 --- a/packages/pyright-internal/src/localization/package.nls.en-us.json +++ b/packages/pyright-internal/src/localization/package.nls.en-us.json @@ -779,6 +779,10 @@ "message": "Bytes and str values cannot be concatenated", "comment": ["{Locked='str'}", "{StrContains=i'bytes'}", "'bytes' is a keyword and should not be localized. It is only capitalized here because it is the first word in the sentence"] }, + "mixingTemplateAndStr": { + "message": "Template string literals (t-strings) cannot be concatenated with string or bytes literals", + "comment": ["'t-string' is the common English slang for a Python template string", "{Locked='bytes'}"] + }, "moduleAsType": "Module cannot be used as a type", "moduleNotCallable": "Module is not callable", "moduleUnknownMember": "\"{memberName}\" is not a known attribute of module \"{moduleName}\"", diff --git a/packages/pyright-internal/src/tests/samples/tstring2.py b/packages/pyright-internal/src/tests/samples/tstring2.py index 0cec4f070785..a00b43b50eb1 100644 --- a/packages/pyright-internal/src/tests/samples/tstring2.py +++ b/packages/pyright-internal/src/tests/samples/tstring2.py @@ -11,10 +11,29 @@ t3 = Tr"" reveal_type(t3, expected_text="Template") -t4 = "" tR"" T"" r"" RT"""{age}""" """x""" +# Implicit concatenation of t-string literals is allowed. +t4 = t"Hello " t"{age}" reveal_type(t4, expected_text="Template") t4.strings t4.interpolations t4.values +# This should generate an error because t-string literals cannot be +# mixed with string literals. +t5 = "" t"x" + +# This should generate an error. +t6 = t"x" "y" + +# This should generate an error. +t7 = t"x" f"y" + +# This should generate an error. +t8 = t"x" b"y" + +t9 = t"a" + t"b" +reveal_type(t9, expected_text="Template") + +# This should generate an error because Template and str cannot be added. +t10 = t"a" + "b" diff --git a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts index acb7d070a9ab..519a2145d1cf 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts @@ -140,7 +140,7 @@ test('TString2', () => { configOptions.defaultPythonVersion = pythonVersion3_14; const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['tstring2.py'], configOptions); - TestUtils.validateResults(analysisResults1, 1); + TestUtils.validateResults(analysisResults1, 6); }); test('MemberAccess1', () => {