Skip to content

Commit

Permalink
Emit error if condition can't be inferred
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p committed Mar 2, 2022
1 parent 3d0397a commit 914d517
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 22 deletions.
49 changes: 40 additions & 9 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,19 @@ def fail(self,
msg: str,
line: int,
column: int,
blocker: bool = True) -> None:
blocker: bool = True,
code: codes.ErrorCode = codes.SYNTAX) -> None:
if blocker or not self.options.ignore_errors:
self.errors.report(line, column, msg, blocker=blocker, code=codes.SYNTAX)
self.errors.report(line, column, msg, blocker=blocker, code=code)

def fail_merge_overload(self, node: IfStmt) -> None:
self.fail(
"Condition can't be inferred, unable to merge overloads",
line=node.line,
column=node.column,
blocker=False,
code=codes.MISC,
)

def visit(self, node: Optional[AST]) -> Any:
if node is None:
Expand Down Expand Up @@ -479,10 +489,12 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
last_if_stmt: Optional[IfStmt] = None
last_if_overload: Optional[Union[Decorator, FuncDef, OverloadedFuncDef]] = None
last_if_stmt_overload_name: Optional[str] = None
last_if_unknown_truth_value: Optional[IfStmt] = None
skipped_if_stmts: List[IfStmt] = []
for stmt in stmts:
if_overload_name: Optional[str] = None
if_block_with_overload: Optional[Block] = None
if_unknown_truth_value: Optional[IfStmt] = None
if (
isinstance(stmt, IfStmt)
and len(stmt.body[0].body) == 1
Expand All @@ -495,7 +507,8 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
# Check IfStmt block to determine if function overloads can be merged
if_overload_name = self._check_ifstmt_for_overloads(stmt)
if if_overload_name is not None:
if_block_with_overload = self._get_executable_if_block_with_overloads(stmt)
if_block_with_overload, if_unknown_truth_value = \
self._get_executable_if_block_with_overloads(stmt)

if (current_overload_name is not None
and isinstance(stmt, (Decorator, FuncDef))
Expand All @@ -510,6 +523,9 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
else:
current_overload.append(last_if_overload)
last_if_stmt, last_if_overload = None, None
if last_if_unknown_truth_value:
self.fail_merge_overload(last_if_unknown_truth_value)
last_if_unknown_truth_value = None
current_overload.append(stmt)
elif (
current_overload_name is not None
Expand All @@ -522,6 +538,8 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
# or function definitions.
skipped_if_stmts.append(stmt)
if if_block_with_overload is None:
if if_unknown_truth_value is not None:
self.fail_merge_overload(if_unknown_truth_value)
continue
if last_if_overload is not None:
# Last stmt was an IfStmt with same overload name
Expand All @@ -542,6 +560,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
ret.append(last_if_stmt)
last_if_stmt_overload_name = current_overload_name
last_if_stmt, last_if_overload = None, None
last_if_unknown_truth_value = None

if current_overload and current_overload_name == last_if_stmt_overload_name:
# Remove last stmt (IfStmt) from ret if the overload names matched
Expand Down Expand Up @@ -580,6 +599,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
Union[Decorator, FuncDef, OverloadedFuncDef],
if_block_with_overload.body[0]
)
last_if_unknown_truth_value = if_unknown_truth_value
else:
current_overload = []
current_overload_name = None
Expand Down Expand Up @@ -630,29 +650,40 @@ def _check_ifstmt_for_overloads(self, stmt: IfStmt) -> Optional[str]:

return None

def _get_executable_if_block_with_overloads(self, stmt: IfStmt) -> Optional[Block]:
def _get_executable_if_block_with_overloads(
self, stmt: IfStmt
) -> Tuple[Optional[Block], Optional[IfStmt]]:
"""Return block from IfStmt that will get executed.
Only returns block if sure that alternative blocks are unreachable.
Return
0 -> A block if sure that alternative blocks are unreachable.
1 -> An IfStmt if the reachability of it can't be inferred,
i.e. the truth value is unknown.
"""
infer_reachability_of_if_statement(stmt, self.options)
if (
stmt.else_body is None
and stmt.body[0].is_unreachable is True
):
# always False condition with no else
return None, None
if (
stmt.else_body is None
or stmt.body[0].is_unreachable is False
and stmt.else_body.is_unreachable is False
):
# The truth value is unknown, thus not conclusive
return None
return None, stmt
if stmt.else_body.is_unreachable is True:
# else_body will be set unreachable if condition is always True
return stmt.body[0]
return stmt.body[0], None
if stmt.body[0].is_unreachable is True:
# body will be set unreachable if condition is always False
# else_body can contain an IfStmt itself (for elif) -> do a recursive check
if isinstance(stmt.else_body.body[0], IfStmt):
return self._get_executable_if_block_with_overloads(stmt.else_body.body[0])
return stmt.else_body
return None
return stmt.else_body, None
return None, stmt

def _strip_contents_from_if_stmt(self, stmt: IfStmt) -> None:
"""Remove contents from IfStmt.
Expand Down
64 changes: 51 additions & 13 deletions test-data/unit/check-overloading.test
Original file line number Diff line number Diff line change
Expand Up @@ -5463,7 +5463,8 @@ reveal_type(f2(C())) # E: No overload variant of "f2" matches argument type "C"
def f3(g: A) -> A: ...
@overload
def f3(g: B) -> B: ...
if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f3(g: C) -> C: ...
def f3(g): ...
Expand Down Expand Up @@ -5777,7 +5778,8 @@ reveal_type(f2(C())) # E: No overload variant of "f2" matches argument type "C"

@overload # E: Single overload definition, multiple required
def f3(x: A) -> A: ...
if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f3(x: B) -> B: ...
elif True:
Expand All @@ -5795,7 +5797,8 @@ reveal_type(f3(B())) # E: No overload variant of "f3" matches argument type "B"

@overload # E: Single overload definition, multiple required
def f4(x: A) -> A: ...
if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f4(x: B) -> B: ...
else:
Expand All @@ -5816,6 +5819,7 @@ class A: ...
class B: ...
class C: ...
class D: ...
class E: ...

# -----
# Match the first always-true block
Expand Down Expand Up @@ -5844,7 +5848,8 @@ def f2(x: A) -> A: ...
if False:
@overload
def f2(x: B) -> B: ...
elif maybe_true: # E: Name "maybe_true" is not defined
elif maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f2(x: C) -> C: ...
else:
Expand All @@ -5859,7 +5864,8 @@ reveal_type(f2(C())) # E: No overload variant of "f2" matches argument type "C"

@overload # E: Single overload definition, multiple required
def f3(x: A) -> A: ...
if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f3(x: B) -> B: ...
elif False:
Expand All @@ -5875,6 +5881,30 @@ reveal_type(f3(B())) # E: No overload variant of "f3" matches argument type "B"
# N: def f3(x: A) -> A \
# N: Revealed type is "Any"

def g(bool_var: bool) -> None:
@overload
def f4(x: A) -> A: ...
if bool_var: # E: Condition cannot be inferred, unable to merge overloads
@overload
def f4(x: B) -> B: ...
elif maybe_true: # E: Name "maybe_true" is not defined
# No 'Condition cannot be inferred' error here since it's already
# emitted on the first condition, 'bool_var', above.
@overload
def f4(x: C) -> C: ...
else:
@overload
def f4(x: D) -> D: ...
@overload
def f4(x: E) -> E: ...
def f4(x): ...
reveal_type(f4(E())) # N: Revealed type is "__main__.E"
reveal_type(f4(B())) # E: No overload variant of "f4" matches argument type "B" \
# N: Possible overload variants: \
# N: def f4(x: A) -> A \
# N: def f4(x: E) -> E \
# N: Revealed type is "Any"

[case testOverloadIfSkipUnknownExecution]
# flags: --always-true True
from typing import overload
Expand All @@ -5891,13 +5921,15 @@ class D: ...

@overload # E: Single overload definition, multiple required
def f1(x: A) -> A: ...
if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f1(x: B) -> B: ...
def f1(x): ...
reveal_type(f1(A())) # N: Revealed type is "__main__.A"

if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f2(x: A) -> A: ...
@overload
Expand All @@ -5914,14 +5946,16 @@ reveal_type(f2(A())) # E: No overload variant of "f2" matches argument type "A"
if True:
@overload # E: Single overload definition, multiple required
def f3(x: A) -> A: ...
if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f3(x: B) -> B: ...
def f3(x): ...
reveal_type(f3(A())) # N: Revealed type is "__main__.A"

if True:
if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f4(x: A) -> A: ...
@overload
Expand Down Expand Up @@ -6178,10 +6212,12 @@ reveal_type(f8(C())) # E: No overload variant of "f8" matches argument type "C"
# N: def f8(x: B) -> B \
# N: Revealed type is "Any"

if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f9(x: A) -> A: ...
if another_maybe_true: # E: Name "another_maybe_true" is not defined
if another_maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "another_maybe_true" is not defined
@overload
def f9(x: B) -> B: ...
@overload
Expand All @@ -6197,10 +6233,12 @@ reveal_type(f9(A())) # E: No overload variant of "f9" matches argument type "A"
reveal_type(f9(C())) # N: Revealed type is "__main__.C"

if True:
if maybe_true: # E: Name "maybe_true" is not defined
if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "maybe_true" is not defined
@overload
def f10(x: A) -> A: ...
if another_maybe_true: # E: Name "another_maybe_true" is not defined
if another_maybe_true: # E: Condition cannot be inferred, unable to merge overloads \
# E: Name "another_maybe_true" is not defined
@overload
def f10(x: B) -> B: ...
@overload
Expand Down

0 comments on commit 914d517

Please sign in to comment.