Skip to content
Open
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
14 changes: 14 additions & 0 deletions crates/pyrefly_config/src/error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,14 @@ pub enum ErrorKind {
NameMismatch,
/// The attribute exists but does not support this access pattern.
NoAccess,
/// Umbrella error kind for cases where `Any` is returned from a function with a concrete return type.
NoAnyReturn,
/// An explicit `Any` returned from a function with a concrete return type.
/// This is a sub-kind of [NoAnyReturn]: suppressing `no-any-return` also suppresses this error.
NoAnyReturnExplicit,
/// An implicit `Any` returned from a function with a concrete return type.
/// This is a sub-kind of [NoAnyReturn]: suppressing `no-any-return` also suppresses this error.
NoAnyReturnImplicit,
/// Attempting to call an overloaded function, but none of the signatures match.
NoMatchingOverload,
/// The SCC fixpoint iteration did not converge within the maximum number of
Expand Down Expand Up @@ -437,6 +445,9 @@ impl ErrorKind {
| ErrorKind::ImplicitAnyEmptyContainer
| ErrorKind::ImplicitAnyParameter
| ErrorKind::ImplicitAnyTypeArgument => Some(ErrorKind::ImplicitAny),
ErrorKind::NoAnyReturnExplicit | ErrorKind::NoAnyReturnImplicit => {
Some(ErrorKind::NoAnyReturn)
}
_ => None,
}
}
Expand Down Expand Up @@ -481,6 +492,9 @@ impl ErrorKind {
ErrorKind::MissingOverrideDecorator => Severity::Ignore,
ErrorKind::MissingSource => Severity::Ignore,
ErrorKind::NameMismatch => Severity::Warn,
ErrorKind::NoAnyReturn => Severity::Ignore,
ErrorKind::NoAnyReturnExplicit => Severity::Ignore,
ErrorKind::NoAnyReturnImplicit => Severity::Ignore,
ErrorKind::NonExhaustiveMatch => Severity::Warn,
ErrorKind::NonConvergentRecursion => Severity::Warn,
ErrorKind::NotRequiredKeyAccess => Severity::Ignore,
Expand Down
2 changes: 2 additions & 0 deletions crates/pyrefly_config/src/migration/error_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ impl ConfigOptionMigrater for ErrorCodes {
mypy_cfg: &Ini,
pyrefly_cfg: &mut ConfigFile,
) -> anyhow::Result<()> {
let warn_return_any = util::get_bool_or_default(mypy_cfg, "mypy", "warn_return_any");
let warn_redundant_casts =
util::get_bool_or_default(mypy_cfg, "mypy", "warn_redundant_casts");
let disallow_untyped_defs =
Expand All @@ -39,6 +40,7 @@ impl ConfigOptionMigrater for ErrorCodes {
util::get_bool_or_default(mypy_cfg, "mypy", "allow_redefinitions");
let strict = util::get_bool_or_default(mypy_cfg, "mypy", "strict");
let mypy_flags = MypyErrorConfigFlags {
warn_return_any,
warn_redundant_casts,
disallow_untyped_defs,
disallow_incomplete_defs,
Expand Down
6 changes: 6 additions & 0 deletions crates/pyrefly_config/src/migration/mypy/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub fn expand_config_file_dir(path: &str) -> String {

#[derive(Default)]
pub struct MypyErrorConfigFlags {
pub warn_return_any: bool,
pub warn_redundant_casts: bool,
pub disallow_untyped_defs: bool,
pub disallow_incomplete_defs: bool,
Expand All @@ -143,6 +144,7 @@ pub fn make_error_config(
errors.insert(error_code, Severity::Error);
}
if let Some(MypyErrorConfigFlags {
warn_return_any,
warn_redundant_casts,
disallow_untyped_defs,
disallow_incomplete_defs,
Expand All @@ -154,6 +156,9 @@ pub fn make_error_config(
}) = mypy_error_config_flags
{
// These severities take precedence over enable/disable
if warn_return_any {
errors.insert(ErrorKind::NoAnyReturn.to_name().to_owned(), Severity::Error);
}
if warn_redundant_casts || strict {
errors.insert(
ErrorKind::RedundantCast.to_name().to_owned(),
Expand Down Expand Up @@ -243,6 +248,7 @@ fn code_to_kind(errors: HashMap<String, Severity>) -> Option<ErrorDisplayConfig>
}
"deprecated" => add(severity, ErrorKind::Deprecated),
"name-match" => add(severity, ErrorKind::NameMismatch),
"no-any-return" => add(severity, ErrorKind::NoAnyReturn),
_ => {}
}
}
Expand Down
56 changes: 53 additions & 3 deletions pyrefly/lib/alt/solve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3674,7 +3674,9 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
.with_annotation(annot_range, "declared return type".to_owned())
};
if let Some(expr) = &x.expr {
self.expr_check(expr, hint.as_ref().map(|t| (t, tcc)), errors)
let return_ty = self.expr_check(expr, hint.as_ref().map(|t| (t, tcc)), errors);
self.check_any_return(&hint, &return_ty, expr.as_ref(), errors);
return_ty
} else if let Some(hint) = hint {
let none = self.heap.mk_none();
self.check_type(&none, &hint, x.range, errors, tcc);
Expand All @@ -3683,11 +3685,14 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
self.heap.mk_none()
}
} else if matches!(hint, Some(Type::TypeGuard(_) | Type::TypeIs(_))) {
let declared_hint = hint; // outer hint (shadowed below)
let hint = Some(self.heap.mk_class_type(self.stdlib.bool().clone()));
let tcc: &dyn Fn() -> TypeCheckContext =
&|| TypeCheckContext::of_kind(TypeCheckKind::TypeGuardReturn);
if let Some(expr) = &x.expr {
self.expr_check(expr, hint.as_ref().map(|t| (t, tcc)), errors)
let return_ty = self.expr_check(expr, hint.as_ref().map(|t| (t, tcc)), errors);
self.check_any_return(&declared_hint, &return_ty, expr.as_ref(), errors);
return_ty
} else if let Some(hint) = hint {
let none = self.heap.mk_none();
self.check_type(&none, &hint, x.range, errors, tcc);
Expand All @@ -3702,7 +3707,9 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
.with_annotation(annot_range, "declared return type".to_owned())
};
if let Some(expr) = &x.expr {
self.expr_check(expr, hint.as_ref().map(|t| (t, tcc)), errors)
let return_ty = self.expr_check(expr, hint.as_ref().map(|t| (t, tcc)), errors);
self.check_any_return(&hint, &return_ty, expr.as_ref(), errors);
return_ty
} else if let Some(hint) = hint {
let none = self.heap.mk_none();
self.check_type(&none, &hint, x.range, errors, tcc);
Expand All @@ -3713,6 +3720,49 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
}
}

/// Check if returning an Any-typed expression from a function with a concrete return type,
/// and emit the appropriate error (NoAnyReturnExplicit or NoAnyReturnImplicit).
fn check_any_return(
&self,
hint: &Option<Type>,
return_ty: &Type,
expr_range: impl Ranged,
errors: &crate::error::collector::ErrorCollector,
) {
let Some(declared_ty) = &hint else { return };
if declared_ty.is_any() {
return;
}
match return_ty {
Type::Any(AnyStyle::Explicit) => {
self.error(
errors,
expr_range.range(),
ErrorKind::NoAnyReturnExplicit,
format!(
"Returning Any from function declared to return \"{}\"",
self.for_display(declared_ty.clone())
),
);
}
// Grouping Error with Implicit is intentional. When analysis fails to recover an
// explicit type (e.g., undefined name), we fall back to AnyStyle::Error. Treating
// it as implicit allows surfacing of the no-any-return-implicit diagnostic.
Type::Any(AnyStyle::Implicit | AnyStyle::Error) => {
self.error(
errors,
expr_range.range(),
ErrorKind::NoAnyReturnImplicit,
format!(
"Returning implicit Any from function declared to return \"{}\"",
self.for_display(declared_ty.clone())
),
);
}
_ => {}
}
}

/// Handle `Binding::ReturnImplicit` - compute the implicit return type.
/// The `#[inline(never)]` annotation is intentional to reduce stack frame size.
#[inline(never)]
Expand Down
Loading
Loading