Skip to content
Closed
17 changes: 14 additions & 3 deletions pyrefly/lib/binding/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use crate::types::types::Type;

struct Decorators {
has_no_type_check: bool,
is_overload: bool,
decorators: Box<[Idx<Key>]>,
}

Expand Down Expand Up @@ -398,15 +399,25 @@ impl<'a> BindingsBuilder<'a> {
}

fn decorators(&mut self, decorator_list: Vec<Decorator>, usage: Usage) -> Decorators {
let has_no_type_check = decorator_list
let (is_overload, has_no_type_check) = decorator_list
.iter()
.any(|d| self.as_special_export(&d.expression) == Some(SpecialExport::NoTypeCheck));
.fold((false, false), |(overload, no_check), d| {
if overload && no_check {
return (true, true);
}
let special_export = self.as_special_export(&d.expression);
(
overload || matches!(special_export, Some(SpecialExport::Overload)),
no_check || matches!(special_export, Some(SpecialExport::NoTypeCheck)),
)
});

let decorators = self
.ensure_and_bind_decorators(decorator_list, usage)
.into_boxed_slice();
Decorators {
has_no_type_check,
is_overload,
decorators,
}
}
Expand All @@ -423,7 +434,7 @@ impl<'a> BindingsBuilder<'a> {
function_idx: Idx<KeyFunction>,
class_key: Option<Idx<KeyClass>>,
) -> (FunctionStubOrImpl, Option<SelfAssignments>) {
let stub_or_impl = if is_ellipse(&body) {
let stub_or_impl = if is_ellipse(&body) || (is_docstring(&body[0]) && decorators.is_overload) {
FunctionStubOrImpl::Stub
} else {
FunctionStubOrImpl::Impl
Expand Down
3 changes: 3 additions & 0 deletions pyrefly/lib/export/special.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub enum SpecialExport {
OsExit,
Len,
NoTypeCheck,
Overload,
}

impl SpecialExport {
Expand Down Expand Up @@ -66,6 +67,7 @@ impl SpecialExport {
"_exit" => Some(Self::OsExit),
"len" => Some(Self::Len),
"no_type_check" => Some(Self::NoTypeCheck),
"overload" => Some(Self::Overload),
_ => None,
}
}
Expand All @@ -85,6 +87,7 @@ impl SpecialExport {
| Self::Optional
| Self::AssertType
| Self::NoTypeCheck
| Self::Overload
| Self::Cast => {
matches!(m.as_str(), "typing" | "typing_extensions")
}
Expand Down
45 changes: 45 additions & 0 deletions pyrefly/lib/test/overload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,3 +386,48 @@ def g(x: int):
f(C(x))
"#,
);

testcase!(
test_overload_with_docstring,
r#"
from typing import overload, Any

@overload
def foo(a: int) -> int: ...

@overload
def foo(a: str) -> str:
"""Docstring"""

def foo(*args, **kwargs) -> Any:
pass

"#,
);

testcase!(
test_overload_with_docstring2,
r#"
from typing import overload, Any

@overload
def foo(a: int) -> int: ...

@overload
def foo(a: str) -> str:
"""Docstring"""
return 123 # E: Returned type `Literal[123]` is not assignable to declared return type `str`

def foo(*args, **kwargs) -> Any:
pass

"#,
);

testcase!(
test_overload_with_docstring3,
r#"
def foo() -> int: # E: Function declared to return `int` but is missing an explicit `return`
"""hello"""
"#,
);
Loading