From 5bf8f53afa8a9085a5c08deed9c1418c12ccc6d8 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Wed, 20 Aug 2025 18:25:20 -0400 Subject: [PATCH] Fail gracefully on unsupported template strings (PEP 750) --- mypy/fastparse.py | 22 ++++++++++++++++++++++ mypy/test/testcheck.py | 2 ++ mypy/test/testparse.py | 4 ++++ test-data/unit/check-python314.test | 3 +++ test-data/unit/parse-python314.test | 5 +++++ 5 files changed, 36 insertions(+) create mode 100644 test-data/unit/check-python314.test create mode 100644 test-data/unit/parse-python314.test diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 0e1b66f0db59..99d5c48c92d7 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -188,6 +188,13 @@ def ast3_parse( ast_TypeVar = Any ast_TypeVarTuple = Any +if sys.version_info >= (3, 14): + ast_TemplateStr = ast3.TemplateStr + ast_Interpolation = ast3.Interpolation +else: + ast_TemplateStr = Any + ast_Interpolation = Any + N = TypeVar("N", bound=Node) # There is no way to create reasonable fallbacks at this stage, @@ -1705,6 +1712,21 @@ def visit_FormattedValue(self, n: ast3.FormattedValue) -> Expression: ) return self.set_line(result_expression, n) + # TemplateStr(expr* values) + def visit_TemplateStr(self, n: ast_TemplateStr) -> Expression: + self.fail( + ErrorMessage("PEP 750 template strings are not yet supported"), + n.lineno, + n.col_offset, + blocker=False, + ) + e = TempNode(AnyType(TypeOfAny.from_error)) + return self.set_line(e, n) + + # Interpolation(expr value, constant str, int conversion, expr? format_spec) + def visit_Interpolation(self, n: ast_Interpolation) -> Expression: + assert False, "Unreachable" + # Attribute(expr value, identifier attr, expr_context ctx) def visit_Attribute(self, n: Attribute) -> MemberExpr | SuperExpr: value = n.value diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 206eab726a21..04ef5370d381 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -47,6 +47,8 @@ typecheck_files.remove("check-python312.test") if sys.version_info < (3, 13): typecheck_files.remove("check-python313.test") +if sys.version_info < (3, 14): + typecheck_files.remove("check-python314.test") class TypeCheckSuite(DataSuite): diff --git a/mypy/test/testparse.py b/mypy/test/testparse.py index 027ca4dd2887..c8bcb5c0d807 100644 --- a/mypy/test/testparse.py +++ b/mypy/test/testparse.py @@ -27,6 +27,8 @@ class ParserSuite(DataSuite): files.remove("parse-python312.test") if sys.version_info < (3, 13): files.remove("parse-python313.test") + if sys.version_info < (3, 14): + files.remove("parse-python314.test") def run_case(self, testcase: DataDrivenTestCase) -> None: test_parser(testcase) @@ -46,6 +48,8 @@ def test_parser(testcase: DataDrivenTestCase) -> None: options.python_version = (3, 12) elif testcase.file.endswith("python313.test"): options.python_version = (3, 13) + elif testcase.file.endswith("python314.test"): + options.python_version = (3, 14) else: options.python_version = defaults.PYTHON3_VERSION diff --git a/test-data/unit/check-python314.test b/test-data/unit/check-python314.test new file mode 100644 index 000000000000..f1043aab860a --- /dev/null +++ b/test-data/unit/check-python314.test @@ -0,0 +1,3 @@ +[case testTemplateString] +reveal_type(t"mypy") # E: PEP 750 template strings are not yet supported \ + # N: Revealed type is "Any" diff --git a/test-data/unit/parse-python314.test b/test-data/unit/parse-python314.test new file mode 100644 index 000000000000..34fe753084f6 --- /dev/null +++ b/test-data/unit/parse-python314.test @@ -0,0 +1,5 @@ +[case testTemplateString] +x = 'mypy' +t'Hello {x}' +[out] +main:2: error: PEP 750 template strings are not yet supported