Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
1 change: 1 addition & 0 deletions packages/pyright-internal/src/localization/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}\"",
Expand Down
21 changes: 20 additions & 1 deletion packages/pyright-internal/src/tests/samples/tstring2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning · Non-blocking recommendation

The tests only contribute to an aggregate diagnostic count and do not verify the new Unknown recovery type. Add reveal_type coverage for at least one mixed expression so a regression to Template inference cannot pass.

[verified]


# 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"
2 changes: 1 addition & 1 deletion packages/pyright-internal/src/tests/typeEvaluator4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down