Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[flake8-return] Recognize functions returning Never as non-returning (RET503) #15298

Merged
merged 2 commits into from
Jan 7, 2025
Merged
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
60 changes: 50 additions & 10 deletions crates/ruff_linter/resources/test/fixtures/flake8_return/RET503.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,17 +331,16 @@ def end_of_file():


# function return type annotation NoReturn
def bar_no_return_annotation() -> NoReturn:
abort()

def foo(x: int) -> int:
def bar() -> NoReturn:
abort()
if x == 5:
return 5
bar()
bar_no_return_annotation()


def foo(string: str) -> str:
def raises(value: str) -> NoReturn:
raise RuntimeError("something went wrong")

match string:
case "a":
Expand All @@ -351,23 +350,49 @@ def raises(value: str) -> NoReturn:
case "c":
return "third"
case _:
raises(string)
bar_no_return_annotation()


def foo() -> int:
def baz() -> int:
return 1

if baz() > 3:
return 1
bar_no_return_annotation()

def bar() -> NoReturn:
a = 1 + 2
raise AssertionError("Very bad")


# function return type annotation typing_extensions.Never
def bar_never_annotation() -> typing_extensions.Never:
abort()

def foo(x: int) -> int:
if x == 5:
return 5
bar_never_annotation()


def foo(string: str) -> str:

match string:
case "a":
return "first"
case "b":
return "second"
case "c":
return "third"
case _:
bar_never_annotation()


def foo() -> int:
def baz() -> int:
return 1

if baz() > 3:
return 1
bar()
bar_never_annotation()


def f():
Expand All @@ -376,3 +401,18 @@ def f():
else:
with c:
d



# The rule shouldn't generate a diagnostic for functions where one branch
# calls a nested function annotated with `NoReturn` or `Never`.
# However, the rule isn't handling this case correctly yet.
# This is because looking up `bar` fails when analysing `foo` because
# the semantic model hasn't yet seen `bar`'s declaration.
# Supporting nested functions requires making this a deferred rule.
def foo(x: int) -> int:
def bar() -> NoReturn:
abort()
if x == 5:
return 5
bar()
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
};

semantic.match_typing_qualified_name(&qualified_name, "NoReturn")
|| semantic.match_typing_qualified_name(&qualified_name, "Never")
}

fn add_return_none(checker: &mut Checker, stmt: &Stmt, range: TextRange) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,71 +403,37 @@ RET503.py:329:5: RET503 [*] Missing explicit `return` at the end of function abl
332 333 |
333 334 | # function return type annotation NoReturn

RET503.py:339:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
RET503.py:403:13: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
337 | if x == 5:
338 | return 5
339 | bar()
| ^^^^^ RET503
|
= help: Add explicit `return` statement

ℹ Unsafe fix
337 337 | if x == 5:
338 338 | return 5
339 339 | bar()
340 |+ return None
340 341 |
341 342 |
342 343 | def foo(string: str) -> str:

RET503.py:354:13: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
352 | return "third"
353 | case _:
354 | raises(string)
| ^^^^^^^^^^^^^^ RET503
401 | else:
402 | with c:
403 | d
| ^ RET503
|
= help: Add explicit `return` statement

ℹ Unsafe fix
352 352 | return "third"
353 353 | case _:
354 354 | raises(string)
355 |+ return None
355 356 |
356 357 |
357 358 | def foo() -> int:

RET503.py:370:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
368 | if baz() > 3:
369 | return 1
370 | bar()
401 401 | else:
402 402 | with c:
403 403 | d
404 |+ return None
404 405 |
405 406 |
406 407 |

RET503.py:418:5: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
416 | if x == 5:
417 | return 5
418 | bar()
| ^^^^^ RET503
|
= help: Add explicit `return` statement

ℹ Unsafe fix
368 368 | if baz() > 3:
369 369 | return 1
370 370 | bar()
371 |+ return None
371 372 |
372 373 |
373 374 | def f():

RET503.py:378:13: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
376 | else:
377 | with c:
378 | d
| ^ RET503
|
= help: Add explicit `return` statement

ℹ Unsafe fix
376 376 | else:
377 377 | with c:
378 378 | d
379 |+ return None
415 415 | abort()
416 416 | if x == 5:
417 417 | return 5
418 |- bar()
418 |+ bar()
419 |+ return None
Original file line number Diff line number Diff line change
Expand Up @@ -411,99 +411,45 @@ RET503.py:326:1: RET503 [*] Missing explicit `return` at the end of function abl
332 333 |
333 334 | # function return type annotation NoReturn

RET503.py:334:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
333 | # function return type annotation NoReturn
334 | / def foo(x: int) -> int:
335 | | def bar() -> NoReturn:
336 | | abort()
337 | | if x == 5:
338 | | return 5
339 | | bar()
| |_________^ RET503
|
= help: Add explicit `return` statement

ℹ Unsafe fix
337 337 | if x == 5:
338 338 | return 5
339 339 | bar()
340 |+ return None
340 341 |
341 342 |
342 343 | def foo(string: str) -> str:

RET503.py:342:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
342 | / def foo(string: str) -> str:
343 | | def raises(value: str) -> NoReturn:
344 | | raise RuntimeError("something went wrong")
345 | |
346 | | match string:
347 | | case "a":
348 | | return "first"
349 | | case "b":
350 | | return "second"
351 | | case "c":
352 | | return "third"
353 | | case _:
354 | | raises(string)
| |__________________________^ RET503
RET503.py:398:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
398 | / def f():
399 | | if a:
400 | | return b
401 | | else:
402 | | with c:
403 | | d
| |_____________^ RET503
|
= help: Add explicit `return` statement

ℹ Unsafe fix
352 352 | return "third"
353 353 | case _:
354 354 | raises(string)
355 |+ return None
355 356 |
356 357 |
357 358 | def foo() -> int:

RET503.py:357:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
357 | / def foo() -> int:
358 | | def baz() -> int:
359 | | return 1
360 | |
361 | |
362 | | def bar() -> NoReturn:
363 | | a = 1 + 2
364 | | raise AssertionError("Very bad")
365 | |
366 | |
367 | |
368 | | if baz() > 3:
369 | | return 1
370 | | bar()
401 401 | else:
402 402 | with c:
403 403 | d
404 |+ return None
404 405 |
405 406 |
406 407 |

RET503.py:413:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
411 | # the semantic model hasn't yet seen `bar`'s declaration.
412 | # Supporting nested functions requires making this a deferred rule.
413 | / def foo(x: int) -> int:
414 | | def bar() -> NoReturn:
415 | | abort()
416 | | if x == 5:
417 | | return 5
418 | | bar()
| |_________^ RET503
|
= help: Add explicit `return` statement

ℹ Unsafe fix
368 368 | if baz() > 3:
369 369 | return 1
370 370 | bar()
371 |+ return None
371 372 |
372 373 |
373 374 | def f():

RET503.py:373:1: RET503 [*] Missing explicit `return` at the end of function able to return non-`None` value
|
373 | / def f():
374 | | if a:
375 | | return b
376 | | else:
377 | | with c:
378 | | d
| |_____________^ RET503
|
= help: Add explicit `return` statement

ℹ Unsafe fix
376 376 | else:
377 377 | with c:
378 378 | d
379 |+ return None
415 415 | abort()
416 416 | if x == 5:
417 417 | return 5
418 |- bar()
418 |+ bar()
419 |+ return None
Loading