From 785b922b28b1912954881ed6997e5051ce4343c8 Mon Sep 17 00:00:00 2001 From: Aryan Bagade <73382554+AryanBagade@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:37:55 -0800 Subject: [PATCH] Fix: Detect type errors for bare return statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 Fixes #1508 --- pyrefly/lib/alt/solve.rs | 21 +++++++++++++++++++++ pyrefly/lib/binding/binding.rs | 1 + pyrefly/lib/binding/function.rs | 1 + pyrefly/lib/test/returns.rs | 27 +++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/pyrefly/lib/alt/solve.rs b/pyrefly/lib/alt/solve.rs index d86a14968a..8d728b6e08 100644 --- a/pyrefly/lib/alt/solve.rs +++ b/pyrefly/lib/alt/solve.rs @@ -2926,6 +2926,27 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { self.expr(expr, hint.as_ref().map(|t| (t, tcc)), errors) } } else { + // Bare return (no value) implicitly returns None + // Check if None is compatible with the declared return type + if let Some(ref ret_type) = hint { + // For generators, extract the return type (3rd type parameter) + let expected_type = if x.is_generator { + hint.and_then(|ty| self.decompose_generator(&ty).map(|(_, _, r)| r)) + } else if matches!(hint, Some(Type::TypeGuard(_) | Type::TypeIs(_))) { + // TypeGuard functions should return bool + Some(Type::ClassType(self.stdlib.bool().clone())) + } else { + Some(ret_type.clone()) + }; + + // Check None against expected return type + if let Some(expected) = expected_type { + let tcc: &dyn Fn() -> TypeCheckContext = &|| { + TypeCheckContext::of_kind(TypeCheckKind::ExplicitFunctionReturn) + }; + self.check_type(&Type::None, &expected, x.range, errors, tcc); + } + } Type::None } } diff --git a/pyrefly/lib/binding/binding.rs b/pyrefly/lib/binding/binding.rs index a4764a1172..edf7e0f977 100644 --- a/pyrefly/lib/binding/binding.rs +++ b/pyrefly/lib/binding/binding.rs @@ -1046,6 +1046,7 @@ pub struct ReturnExplicit { pub expr: Option>, pub is_generator: bool, pub is_async: bool, + pub range: TextRange, } #[derive(Clone, Debug)] diff --git a/pyrefly/lib/binding/function.rs b/pyrefly/lib/binding/function.rs index 331627bcb6..613c4ac917 100644 --- a/pyrefly/lib/binding/function.rs +++ b/pyrefly/lib/binding/function.rs @@ -357,6 +357,7 @@ impl<'a> BindingsBuilder<'a> { expr: x.value, is_generator, is_async, + range: x.range, }), ) }) diff --git a/pyrefly/lib/test/returns.rs b/pyrefly/lib/test/returns.rs index 95c4593075..2c4d8760b9 100644 --- a/pyrefly/lib/test/returns.rs +++ b/pyrefly/lib/test/returns.rs @@ -286,3 +286,30 @@ def f(x: str): pass return f(1) # E: Invalid `return` outside of a function # E: `Literal[1]` is not assignable to parameter `x` with type `str` "#, ); + +testcase!( + test_bare_return_with_non_none_type, + r#" +def test() -> int: + return # E: Returned type `None` is not assignable to declared return type `int` +"#, +); + +testcase!( + test_bare_return_with_none_type, + r#" +def test() -> None: + return # Should work - None is assignable to None +"#, +); + +testcase!( + test_bare_return_in_generator, + r#" +from typing import Generator + +def gen() -> Generator[int, None, str]: + yield 1 + return # E: Returned type `None` is not assignable to declared return type `str` +"#, +);