diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 2e40048cbb58..bd038e254450 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -132,6 +132,10 @@ def with_additional_msg(self, info: str) -> ErrorMessage: KEYWORD_ARGUMENT_REQUIRES_STR_KEY_TYPE: Final = ErrorMessage( 'Keyword argument only valid with "str" key type in call to "dict"' ) +TYPE_FORM_STRING_ARGUMENT_HINT: Final = ( + "If this string was intended as a type expression, wrap it in TypeForm(...) " + "to get a more specific error." +) ALL_MUST_BE_SEQ_STR: Final = ErrorMessage("Type of __all__ must be {}, not {}") INVALID_TYPEDDICT_ARGS: Final = ErrorMessage( "Expected keyword arguments, {...}, or dict(...) in TypedDict constructor" diff --git a/mypy/messages.py b/mypy/messages.py index b58c9e7ac4b6..c10e1d9e52af 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -796,6 +796,13 @@ def incompatible_argument( quote_type_string(expected_type_str), ) expected_type = get_proper_type(expected_type) + if ( + isinstance(context, StrExpr) + and context.as_type is None + and isinstance(expected_type, TypeType) + and expected_type.is_type_form + ): + msg += f". {message_registry.TYPE_FORM_STRING_ARGUMENT_HINT}" if isinstance(expected_type, UnionType): expected_types = get_proper_types(expected_type.items) else: diff --git a/test-data/unit/check-fastparse.test b/test-data/unit/check-fastparse.test index eaa3cd9a14fc..cddaf37fd5a2 100644 --- a/test-data/unit/check-fastparse.test +++ b/test-data/unit/check-fastparse.test @@ -342,6 +342,6 @@ def identity(typx: TypeForm) -> TypeForm: return typx # This should not generate SyntaxWarning despite invalid escape sequence -identity(r"re\.match") # E: Argument 1 to "identity" has incompatible type "str"; expected "TypeForm[Any]" +identity(r"re\.match") # E: Argument 1 to "identity" has incompatible type "str"; expected "TypeForm[Any]". If this string was intended as a type expression, wrap it in TypeForm(...) to get a more specific error. [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/check-typeform.test b/test-data/unit/check-typeform.test index d08826e233f7..553b83350c3c 100644 --- a/test-data/unit/check-typeform.test +++ b/test-data/unit/check-typeform.test @@ -157,6 +157,15 @@ is_type('int | None') [builtins fixtures/primitives.pyi] [typing fixtures/typing-full.pyi] +[case testUnresolvableStringTypeFormArgument] +from typing_extensions import TypeForm, TypeVar +T = TypeVar('T') +def foo(x: TypeForm[T]) -> None: + pass +foo('Unresolvable') # E: Argument 1 to "foo" has incompatible type "str"; expected "TypeForm[Never]". If this string was intended as a type expression, wrap it in TypeForm(...) to get a more specific error. +[builtins fixtures/primitives.pyi] +[typing fixtures/typing-full.pyi] + -- Type Expression Location: Return Statement