Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4433,12 +4433,14 @@ def check_op(
recent_context.line = err.line
recent_context.column = err.column
if len(left_variants) >= 2 and len(right_variants) >= 2:
self.msg.warn_both_operands_are_from_unions(recent_context)
self.msg.warn_both_operands_are_from_unions(recent_context, parent_error=err)
elif len(left_variants) >= 2:
self.msg.warn_operand_was_from_union("Left", base_type, context=recent_context)
self.msg.warn_operand_was_from_union(
"Left", base_type, context=recent_context, parent_error=err
)
elif len(right_variants) >= 2:
self.msg.warn_operand_was_from_union(
"Right", right_type, context=recent_context
"Right", right_type, context=recent_context, parent_error=err
)

# See the comment in 'check_overload_call' for more details on why
Expand Down
11 changes: 10 additions & 1 deletion mypy/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def __init__(
self.code = code
self.blocker = blocker
self.only_once = only_once
self.origin_span = origin_span or [line]
self.origin_span = list(origin_span) if origin_span is not None else [line]
self.target = target
self.priority = priority
if parent_error is not None:
Expand Down Expand Up @@ -776,6 +776,15 @@ def add_error_info(self, info: ErrorInfo, *, file: str | None = None) -> None:
if self._filter_error(file, info):
return
if not info.blocker: # Blockers cannot be ignored
if info.parent_error is not None and file in self.ignored_lines:
# A note attached to an error must not outlive its parent: if
# the parent error is ignored on its own line(s), the note is
# not shown even if it is reported on another line.
for scope_line in info.parent_error.origin_span:
if self.is_ignored_error(
scope_line, info.parent_error, self.ignored_lines[file]
):
return
if file in self.ignored_lines:
# Check each line in this context for "type: ignore" comments.
# line == end_line for most nodes, so we only loop once.
Expand Down
16 changes: 13 additions & 3 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1775,14 +1775,24 @@ def overloaded_signatures_ret_specific(self, index: int, context: Context) -> No
context,
)

def warn_both_operands_are_from_unions(self, context: Context) -> None:
self.note("Both left and right operands are unions", context, code=codes.OPERATOR)
def warn_both_operands_are_from_unions(
self, context: Context, parent_error: ErrorInfo | None = None
) -> None:
self.note(
"Both left and right operands are unions",
context,
code=codes.OPERATOR,
parent_error=parent_error,
)

def warn_operand_was_from_union(self, side: str, original: Type, context: Context) -> None:
def warn_operand_was_from_union(
self, side: str, original: Type, context: Context, parent_error: ErrorInfo | None = None
) -> None:
self.note(
f"{side} operand is of type {format_type(original, self.options)}",
context,
code=codes.OPERATOR,
parent_error=parent_error,
)

def operator_method_signatures_overlap(
Expand Down
10 changes: 10 additions & 0 deletions test-data/unit/check-unions.test
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ x[2] + 1 # E: Unsupported operand types for + ("str" and "int") \
# N: Left operand is of type "int | str"
[builtins fixtures/isinstancelist.pyi]

[case testIgnoredOperatorErrorDoesNotLeakNote]
# A note attached to an error ignored by a "type: ignore" comment on a
# different line of the same multi-line expression must not leak.
a:b
b =dict[0, b|0] # type:ignore[valid-type]
a[0
]+"" # type:ignore[operator]
[builtins fixtures/isinstancelist.pyi]


[case testUnionAsOverloadArg]
from foo import *
x = 0
Expand Down
Loading