From 47bde1a6b40fa1acc4301cb064f8fb3a532c4204 Mon Sep 17 00:00:00 2001 From: jw9829 <245427686+jw9829@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:06:09 -0400 Subject: [PATCH] Improve errors for unresolved TypeForm arguments --- mypy/checkexpr.py | 15 +++++++++++++++ test-data/unit/check-typeform.test | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6cdd64e84302f..d29a2a6f2432d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2810,6 +2810,21 @@ def check_arg( # Only non-abstract non-protocol class can be given where Type[...] is expected... elif self.has_abstract_type_part(caller_type, callee_type): self.msg.concrete_only_call(callee_type, context) + # Semantic analysis tried and failed to parse this as a type expression. + elif ( + isinstance(context, StrExpr) + and context.as_type is None + and isinstance(caller_type, Instance) + and caller_type.type.fullname == "builtins.str" + and isinstance(callee_type, TypeType) + and callee_type.is_type_form + ): + self.chk.fail( + "TypeForm containing a string annotation cannot be recognized here. " + "Surround with TypeForm(...) to recognize.", + context, + code=codes.MAYBE_UNRECOGNIZED_STR_TYPEFORM, + ) elif not is_subtype(caller_type, callee_type, options=self.chk.options): error = self.msg.incompatible_argument( n, diff --git a/test-data/unit/check-typeform.test b/test-data/unit/check-typeform.test index d08826e233f76..d83380888c785 100644 --- a/test-data/unit/check-typeform.test +++ b/test-data/unit/check-typeform.test @@ -157,6 +157,18 @@ is_type('int | None') [builtins fixtures/primitives.pyi] [typing fixtures/typing-full.pyi] +[case testUnrecognizedStringTypeFormArgument] +from typing import TypeVar +from typing_extensions import TypeForm +T = TypeVar("T") +def foo(x: TypeForm[T]) -> None: pass +foo("Unresolvable") # E: TypeForm containing a string annotation cannot be recognized here. Surround with TypeForm(...) to recognize. +foo(42) # E: Argument 1 to "foo" has incompatible type "int"; expected "TypeForm[Never]" +def foo_or_str(x: TypeForm[T] | str) -> None: pass +foo_or_str("Unresolvable") +[builtins fixtures/primitives.pyi] +[typing fixtures/typing-full.pyi] + -- Type Expression Location: Return Statement