diff --git a/mypy/test/data.py b/mypy/test/data.py
index 5b0ad84c0ba7..22c39af04ec1 100644
--- a/mypy/test/data.py
+++ b/mypy/test/data.py
@@ -533,9 +533,9 @@ def expand_errors(input: list[str], output: list[str], fnam: str) -> None:
for i in range(len(input)):
# The first in the split things isn't a comment
- for possible_err_comment in input[i].split(" # ")[1:]:
+ for possible_err_comment in re.split(r"(?!\\)#\s*(?=[ENW]\s*:)", input[i])[1:]:
m = re.search(
- r"^([ENW]):((?P
\d+):)? (?P.*)$", possible_err_comment.strip()
+ r"^([ENW])\s*:((?P\d+):)?(?P.*)$", possible_err_comment.strip()
)
if m:
if m.group(1) == "E":
@@ -546,11 +546,12 @@ def expand_errors(input: list[str], output: list[str], fnam: str) -> None:
severity = "warning"
col = m.group("col")
message = m.group("message")
- message = message.replace("\\#", "#") # adds back escaped # character
+ message = message.replace(r"\#", "#") # adds back escaped # character
+ # Message may, and probably does, include leading spaces
if col is None:
- output.append(f"{fnam}:{i + 1}: {severity}: {message}")
+ output.append(f"{fnam}:{i + 1}: {severity}:{message}")
else:
- output.append(f"{fnam}:{i + 1}:{col}: {severity}: {message}")
+ output.append(f"{fnam}:{i + 1}:{col}: {severity}:{message}")
def fix_win_path(line: str) -> str:
diff --git a/mypy/test/update_data.py b/mypy/test/update_data.py
index 84b6383b3f0c..9269d3380543 100644
--- a/mypy/test/update_data.py
+++ b/mypy/test/update_data.py
@@ -64,7 +64,7 @@ def _iter_fixes(
fix_lines = []
for lineno, source_line in enumerate(source_lines, start=1):
reports = reports_by_line.get((file_path, lineno))
- comment_match = re.search(r"(?P\s+)(?P# [EWN]: .+)$", source_line)
+ comment_match = re.search(r"(?P\s*)(?P#\s*[EWN]\s*:.+)$", source_line)
if comment_match:
source_line = source_line[: comment_match.start("indent")] # strip old comment
if reports:
diff --git a/test-data/unit/README.md b/test-data/unit/README.md
index aaf774d1b62f..1ca66a60caac 100644
--- a/test-data/unit/README.md
+++ b/test-data/unit/README.md
@@ -31,17 +31,23 @@ with text "abc..."
- note a space after `E:` and `flags:`
- `# E:12` adds column number to the expected error
- use `\` to escape the `#` character and indicate that the rest of the line is part of
-the error message
+ the error message (note that there is no support for using `\\` to escape a backslash itself
+ in this context; also, in all other contexts, such as line-continuation, the backslash is treated
+ as it normally would be in a python source file)
- repeating `# E: ` several times in one line indicates multiple expected errors in one line
- `W: ...` and `N: ...` works exactly like `E: ...`, but report a warning and a note respectively
- lines that don't contain the above should cause no type check errors
+- lines that begin with `--` are test-file-format comments, and will not appear in the tested python
+ source code
+- some test files are run in a special way by the test runner; this is typically documented in
+ test-file-format comments at the top of the test file
- optional `[builtins fixtures/...]` tells the type checker to use
`builtins` stubs from the indicated file (see Fixtures section below)
- optional `[out]` is an alternative to the `# E: ` notation: it indicates that
-any text after it contains the expected type checking error messages.
-Usually, `# E: ` is preferred because it makes it easier to associate the
-errors with the code generating them at a glance, and to change the code of
-the test without having to change line numbers in `[out]`
+ any text after it contains the expected type checking error messages.
+ Usually, `# E: ` is preferred because it makes it easier to associate the
+ errors with the code generating them at a glance, and to change the code of
+ the test without having to change line numbers in `[out]`
- an empty `[out]` section has no effect
- to add tests for a feature that hasn't been implemented yet, append `-xfail`
to the end of the test name
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
index f713fe69bcd2..a9f81e54d5a6 100644
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -695,7 +695,7 @@ class A:
def __replace__(self, x: Optional[str]) -> Self: pass
class B(A):
- def __replace__(self, x: str) -> Self: pass # E: \
+ def __replace__(self, x: str) -> Self: pass \
# E: Argument 1 of "__replace__" is incompatible with supertype "A"; supertype defines the argument type as "Optional[str]" [override] \
# N: This violates the Liskov substitution principle \
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
diff --git a/test-data/unit/check-deprecated.test b/test-data/unit/check-deprecated.test
index e1173ac425ba..0ceb3ce4a2c4 100644
--- a/test-data/unit/check-deprecated.test
+++ b/test-data/unit/check-deprecated.test
@@ -49,7 +49,8 @@ from typing_extensions import deprecated
@deprecated("use f2 instead")
def f() -> None: ...
-f # E: function __main__.f is deprecated: use f2 instead # type: ignore[deprecated]
+f # type: ignore[deprecated]
+f # E: function __main__.f is deprecated: use f2 instead
f(1) # E: function __main__.f is deprecated: use f2 instead \
# E: Too many arguments for "f"
f[1] # E: function __main__.f is deprecated: use f2 instead \
diff --git a/test-data/unit/check-error-comments.test b/test-data/unit/check-error-comments.test
new file mode 100644
index 000000000000..948f7660fa59
--- /dev/null
+++ b/test-data/unit/check-error-comments.test
@@ -0,0 +1,49 @@
+[case noErrorComment]
+x: int = 1
+
+[case prototypicalErrorComment]
+x: int = "hi" # E: Incompatible types in assignment (expression has type "str", variable has type "int")
+
+[case emptyLineErrorComment-xfail]
+# E:
+
+[case oddErrorComments]
+x: int = "hi"#E: Incompatible types in assignment (expression has type "str", variable has type "int")
+x2: int = "hi" #E: Incompatible types in assignment (expression has type "str", variable has type "int")
+x3: int = "hi" # E: Incompatible types in assignment (expression has type "str", variable has type "int")
+x4: int = "hi" # E : Incompatible types in assignment (expression has type "str", variable has type "int")
+x5: int = "hi" # E : Incompatible types in assignment (expression has type "str", variable has type "int")
+x6: int = "hi" # E : Incompatible types in assignment (expression has type "str", variable has type "int")
+x7: int = "hi" # E : Incompatible types in assignment (expression has type "str", variable has type "int")
+x8: int = "hi" \
+ # E : Incompatible types in assignment (expression has type "str", variable has type "int")
+x82: int = "hi"\
+# E : Incompatible types in assignment (expression has type "str", variable has type "int")
+
+[case oddErrorCommentsNoteComment]
+n: int
+reveal_type(n) # N: Revealed type is "builtins.int"
+
+
+[case oddErrorCommentsThatDontWork-xfail]
+-- The space between the ":" and the message actually differs in the output, which we match against
+-- so we can't just parse it and replace it; therefore, these do not work.
+x: int = "hi"#E:Incompatible types in assignment (expression has type "str", variable has type "int")
+x2: int = "hi" #E:Incompatible types in assignment (expression has type "str", variable has type "int")
+x3: int = "hi" # E : Incompatible types in assignment (expression has type "str", variable has type "int")
+x4: int = "hi" # E : Incompatible types in assignment (expression has type "str", variable has type "int")
+x5: int = "hi" # E : Incompatible types in assignment (expression has type "str", variable has type "int")
+
+[case oddErrorCommentsThatDontWorkCase-xfail]
+-- I just didn't bother to implement these.
+x: int = "hi" # e: Incompatible types in assignment (expression has type "str", variable has type "int")
+x: int = "hi" # w: Incompatible types in assignment (expression has type "str", variable has type "int")
+x: int = "hi" # n: Incompatible types in assignment (expression has type "str", variable has type "int")
+
+
+[case oddErrorCommentsThatDontWorkCaseAndNoColon-xfail]
+-- I didn't implement these. This is veering towards the ambiguous.
+x: int = "hi" # e: Incompatible types in assignment (expression has type "str", variable has type "int")
+x2: int = "hi" # E Incompatible types in assignment (expression has type "str", variable has type "int")
+x3: int = "hi" # e Incompatible types in assignment (expression has type "str", variable has type "int")
+x4: int = "hi" # e Incompatible types in assignment (expression has type "str", variable has type "int")
diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test
index 61bf08018722..a7564e476804 100644
--- a/test-data/unit/check-newsemanal.test
+++ b/test-data/unit/check-newsemanal.test
@@ -2684,7 +2684,7 @@ class C:
) -> str:
return 0 # E: Incompatible return value type (got "int", expected "str")
-reveal_type(C.X) # E: # N: Revealed type is "def () -> __main__.X"
+reveal_type(C.X) # N: Revealed type is "def () -> __main__.X"
reveal_type(C().str()) # N: Revealed type is "builtins.str"
[case testNewAnalyzerNameNotDefinedYetInClassBody]
diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test
index 5bbb503a578a..e917e83e004b 100644
--- a/test-data/unit/check-type-aliases.test
+++ b/test-data/unit/check-type-aliases.test
@@ -551,12 +551,7 @@ def take_id(x: Id[int]) -> None:
def id(x: Id[T]) -> T:
return x
-# TODO: This doesn't work and maybe it should?
-# Indirection = AnInt[T]
-# y: Indirection[str]
-# reveal_type(y) # E : Revealed type is "builtins.int"
-
-# But this does
+# Contrast with the TODO below
Indirection2 = FlexibleAlias[T, AnInt[T]]
z: Indirection2[str]
reveal_type(z) # N: Revealed type is "builtins.int"
@@ -567,6 +562,16 @@ reveal_type(w) # N: Revealed type is "builtins.int"
[builtins fixtures/dict.pyi]
+[case testFlexibleAlias1Todo-xfail]
+# TODO: This doesn't work and maybe it should?
+# Contrast with the example above that mentions this todo
+T = TypeVar('T')
+AnInt = FlexibleAlias[T, int]
+Indirection = AnInt[T]
+y: Indirection[str]
+reveal_type(y) # N : Revealed type is "builtins.int"
+
+
[case testFlexibleAlias2]
# flags: --always-true=BOGUS
from typing import TypeVar, Any
diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test
index be5a6c655d8c..6676d23f087d 100644
--- a/test-data/unit/check-typeddict.test
+++ b/test-data/unit/check-typeddict.test
@@ -3652,7 +3652,7 @@ reveal_type(X) # N: Revealed type is "builtins.dict[builtins.str, def () -> bui
from typing_extensions import TypeAlias
X: TypeAlias = {"int": int, "str": str}
x: X
-reveal_type(x) # N: # N: Revealed type is "TypedDict({'int': builtins.int, 'str': builtins.str})"
+reveal_type(x) # N: Revealed type is "TypedDict({'int': builtins.int, 'str': builtins.str})"
[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]