Skip to content

Commit fc91a0f

Browse files
AryanBagademeta-codesync[bot]
authored andcommitted
Fix: Detect type errors for bare return statements (#1508) (#1524)
Summary: ## Changes 1. Added `range` field to `ReturnExplicit` struct to track the return statement location 2. Updated `binding/function.rs` to pass the range when creating `ReturnExplicit` 3. Added type checking in `solve.rs` for bare return ## Testing - ✅ Added 3 new test cases in returns.rs <img width="1738" height="2120" alt="CleanShot 2025-11-07 at 10 29 00@2x" src="https://github.com/user-attachments/assets/baae6d23-befc-42b3-8e87-22727d61a441" /> Fixes #1508 Pull Request resolved: #1524 Reviewed By: grievejia Differential Revision: D86550828 Pulled By: yangdanny97 fbshipit-source-id: 38267c7ac0f67a2053eb76bb06dd76e82499ea6a
1 parent d3af51f commit fc91a0f

File tree

4 files changed

+64
-15
lines changed

4 files changed

+64
-15
lines changed

pyrefly/lib/alt/solve.rs

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2908,8 +2908,8 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
29082908
let annot = x.annot.map(|k| self.get_idx(k));
29092909
let hint = annot.as_ref().and_then(|ann| ann.ty(self.stdlib));
29102910

2911-
if let Some(expr) = &x.expr {
2912-
if x.is_async && x.is_generator {
2911+
if x.is_async && x.is_generator {
2912+
if let Some(box expr) = &x.expr {
29132913
self.expr_infer(expr, errors);
29142914
self.error(
29152915
errors,
@@ -2918,24 +2918,44 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
29182918
"Return statement with value is not allowed in async generator"
29192919
.to_owned(),
29202920
)
2921-
} else if x.is_generator {
2922-
let hint =
2923-
hint.and_then(|ty| self.decompose_generator(&ty).map(|(_, _, r)| r));
2924-
let tcc: &dyn Fn() -> TypeCheckContext =
2925-
&|| TypeCheckContext::of_kind(TypeCheckKind::ExplicitFunctionReturn);
2926-
self.expr(expr, hint.as_ref().map(|t| (t, tcc)), errors)
2927-
} else if matches!(hint, Some(Type::TypeGuard(_) | Type::TypeIs(_))) {
2928-
let hint = Some(Type::ClassType(self.stdlib.bool().clone()));
2929-
let tcc: &dyn Fn() -> TypeCheckContext =
2930-
&|| TypeCheckContext::of_kind(TypeCheckKind::TypeGuardReturn);
2921+
} else {
2922+
Type::None
2923+
}
2924+
} else if x.is_generator {
2925+
let hint = hint.and_then(|ty| self.decompose_generator(&ty).map(|(_, _, r)| r));
2926+
let tcc: &dyn Fn() -> TypeCheckContext =
2927+
&|| TypeCheckContext::of_kind(TypeCheckKind::ExplicitFunctionReturn);
2928+
if let Some(box expr) = &x.expr {
29312929
self.expr(expr, hint.as_ref().map(|t| (t, tcc)), errors)
2930+
} else if let Some(hint) = hint {
2931+
self.check_type(&Type::None, &hint, x.range, errors, tcc);
2932+
Type::None
29322933
} else {
2933-
let tcc: &dyn Fn() -> TypeCheckContext =
2934-
&|| TypeCheckContext::of_kind(TypeCheckKind::ExplicitFunctionReturn);
2934+
Type::None
2935+
}
2936+
} else if matches!(hint, Some(Type::TypeGuard(_) | Type::TypeIs(_))) {
2937+
let hint = Some(Type::ClassType(self.stdlib.bool().clone()));
2938+
let tcc: &dyn Fn() -> TypeCheckContext =
2939+
&|| TypeCheckContext::of_kind(TypeCheckKind::TypeGuardReturn);
2940+
if let Some(box expr) = &x.expr {
29352941
self.expr(expr, hint.as_ref().map(|t| (t, tcc)), errors)
2942+
} else if let Some(hint) = hint {
2943+
self.check_type(&Type::None, &hint, x.range, errors, tcc);
2944+
Type::None
2945+
} else {
2946+
Type::None
29362947
}
29372948
} else {
2938-
Type::None
2949+
let tcc: &dyn Fn() -> TypeCheckContext =
2950+
&|| TypeCheckContext::of_kind(TypeCheckKind::ExplicitFunctionReturn);
2951+
if let Some(box expr) = &x.expr {
2952+
self.expr(expr, hint.as_ref().map(|t| (t, tcc)), errors)
2953+
} else if let Some(hint) = hint {
2954+
self.check_type(&Type::None, &hint, x.range, errors, tcc);
2955+
Type::None
2956+
} else {
2957+
Type::None
2958+
}
29392959
}
29402960
}
29412961
Binding::ReturnImplicit(x) => {

pyrefly/lib/binding/binding.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,7 @@ pub struct ReturnExplicit {
10461046
pub expr: Option<Box<Expr>>,
10471047
pub is_generator: bool,
10481048
pub is_async: bool,
1049+
pub range: TextRange,
10491050
}
10501051

10511052
#[derive(Clone, Debug)]

pyrefly/lib/binding/function.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ impl<'a> BindingsBuilder<'a> {
369369
expr: x.value,
370370
is_generator,
371371
is_async,
372+
range: x.range,
372373
}),
373374
)
374375
})

pyrefly/lib/test/returns.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,30 @@ def f(x: str): pass
286286
return f(1) # E: Invalid `return` outside of a function # E: `Literal[1]` is not assignable to parameter `x` with type `str`
287287
"#,
288288
);
289+
290+
testcase!(
291+
test_bare_return_with_non_none_type,
292+
r#"
293+
def test() -> int:
294+
return # E: Returned type `None` is not assignable to declared return type `int`
295+
"#,
296+
);
297+
298+
testcase!(
299+
test_bare_return_with_none_type,
300+
r#"
301+
def test() -> None:
302+
return # Should work - None is assignable to None
303+
"#,
304+
);
305+
306+
testcase!(
307+
test_bare_return_in_generator,
308+
r#"
309+
from typing import Generator
310+
311+
def gen() -> Generator[int, None, str]:
312+
yield 1
313+
return # E: Returned type `None` is not assignable to declared return type `str`
314+
"#,
315+
);

0 commit comments

Comments
 (0)