Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Align t-string/bytes mix error with CPython 3.14 - #27766

Merged
MichaReiser merged 2 commits into
astral-sh:mainfrom
Sacrimento:align-tstrings-errors-w-CPython
Aug 19, 2026
Merged

Align t-string/bytes mix error with CPython 3.14#27766
MichaReiser merged 2 commits into
astral-sh:mainfrom
Sacrimento:align-tstrings-errors-w-CPython

Conversation

@Sacrimento

@Sacrimento Sacrimento commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Implicitly concatenating a t-string with a bytes literal (e.g. t'a' b'b') reported the generic bytes-mixing error instead of the t-string specific one, even though that message already existed for this case. With CPython3.14 implementation, in a mismatched pair of adjacent literals containing a t-string, the t-string error is always reported over a bytes error, regardless of order. This does not apply for pairs not containing t-strings.

This behavior can be observed with the following:

CPython 3.14

$ python3.14
Python 3.14.7 (main, Aug 10 2026, 07:46:56) [GCC 16.1.1 20260728] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> t'a' b'b'
  File "<python-input-0>", line 1
    t'a' b'b'
    ^^^^^^^^^^^
SyntaxError: cannot mix t-string literals with string or bytes literals

Ruff

$ cargo run --bin ruff -- check --target-version py314 --no-cache --isolated - <<EOF                                                                        
heredoc> t'a' b'b'
heredoc> EOF
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/ruff check --target-version py314 --no-cache --isolated -`
invalid-syntax: Bytes literal cannot be mixed with non-bytes literals
 --> -:1:1
  |
1 | t'a' b'b'
  | ^^^^^^^^^^^

Found 1 error.

This change replaces the check with a pairwise scan over adjacent literals, matching CPython's diagnostics.

Test Plan

These changes can be tested using the commands found above: both error messages should be equal.
I also added a unit test inline_err::mixed_tstring_and_bytes_literals.py to ensure ruff always prefers the t-string error as CPython 3.14 does.

Lastly, I have this script (AI-provided) comparing Ruff's and CPython's outputs with all possible combinations of string, bytes, f-strings and t-strings:

cross-check.py
import ast
import itertools
import os
import pathlib
import subprocess

RUFF = pathlib.Path(os.environ("RUFF_REPO")) / "/target/debug/ruff"

KINDS = {
    "S": "'{}'",
    "B": "b'{}'",
    "F": "f'{}'",
    "T": "t'{}'",
}


def build_source(combo):
    parts = [KINDS[k].format(chr(ord("a") + i)) for i, k in enumerate(combo)]
    return " ".join(parts)


def cpython_classify(source):
    try:
        ast.parse(source)
        return "ok"
    except SyntaxError as e:
        msg = str(e.msg)
        if "bytes and nonbytes" in msg:
            return "bytes"
        if "t-string" in msg:
            return "tstring"
        return f"other:{msg}"


def ruff_classify(source):
    proc = subprocess.run(
        [RUFF, "check", "--target-version", "py314", "--no-cache", "--isolated", "-"],
        input=source,
        capture_output=True,
        text=True,
    )
    out = proc.stdout + proc.stderr
    has_bytes = "Bytes literal cannot be mixed" in out
    has_tstring = "Cannot mix t-string literals" in out
    if has_bytes and has_tstring:
        return "both!"
    if has_bytes:
        return "bytes"
    if has_tstring:
        return "tstring"
    # Ignore unrelated diagnostics (e.g. B018/B021 lint noise) - we only care
    # about whether the two string-mixing syntax errors are reported correctly.
    return "ok"


def main():
    mismatches = []
    total = 0
    for length in (2, 3, 4, 5):
        for combo in itertools.product("SBFT", repeat=length):
            total += 1
            source = build_source(combo)
            cpy = cpython_classify(source)
            ruf = ruff_classify(source)
            if cpy != ruf:
                mismatches.append((combo, source, cpy, ruf))

    print(f"Checked {total} combinations (lengths 2-4 over S/B/F/T)")
    print(f"Mismatches: {len(mismatches)}")
    for combo, source, cpy, ruf in mismatches:
        print(f"  {''.join(combo):6} {source!r:45} cpython={cpy!r:12} ruff={ruf!r}")


if __name__ == "__main__":
    main()

@Sacrimento
Sacrimento marked this pull request as ready for review August 14, 2026 21:19
@Sacrimento
Sacrimento force-pushed the align-tstrings-errors-w-CPython branch from a789838 to 0fe5e30 Compare August 16, 2026 18:05
@Sacrimento
Sacrimento requested a review from MichaReiser August 16, 2026 18:27
@astral-sh-bot

astral-sh-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.

Full report with detailed diff (timing results)

Comment thread crates/ruff_python_parser/src/parser/expression.rs
@astral-sh-bot

astral-sh-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

Implicitly concatenating a t-string with a bytes literal
(e.g. `t'a' b'b'`) reported the generic bytes-mixing
error instead of the t-string-specific one, even though
that message already existed for this case.

CPython3.14's implementation reports the first incompatible
pair of strings. However a t-string mismatch always takes
precedence over a bytes mismatch within that pair.

This change replaces the check with a pairwise scan over
adjacent literals, matching CPython's diagnostics.
@Sacrimento
Sacrimento force-pushed the align-tstrings-errors-w-CPython branch from 0fe5e30 to 460a72c Compare August 18, 2026 18:57
@Sacrimento
Sacrimento requested a review from MichaReiser August 18, 2026 19:00

@MichaReiser MichaReiser left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you

@MichaReiser MichaReiser added parser Related to the parser diagnostics Related to reporting of diagnostics. labels Aug 19, 2026
@MichaReiser
MichaReiser merged commit 3ea20f6 into astral-sh:main Aug 19, 2026
98 of 105 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

diagnostics Related to reporting of diagnostics. parser Related to the parser

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants