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
30 changes: 10 additions & 20 deletions pyrefly/lib/commands/coverage/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,7 @@ fn merge_overloads(functions: &mut Vec<Function>) {

for &idx in &indices {
let func = &functions[idx];
let short_name = func.name.rsplit('.').next().unwrap_or(&func.name);
let has_annotation = func.return_annotation.is_some();
let ret = if !has_annotation && is_implicit_dunder_return(short_name) {
SlotRank::Skip
} else {
SlotRank::classify(has_annotation, func.is_return_type_known)
};
return_rank = return_rank.max(ret);
return_rank = return_rank.max(func.return_rank);

for param in &func.parameters {
if let Some(key) = &param.merge_key {
Expand Down Expand Up @@ -772,23 +765,20 @@ fn parse_functions(
.and_then(|t| t.property_metadata().map(|m| m.role.clone()));
let is_property_deleter = matches!(property_role, Some(PropertyRole::DeleterDecorator));

// Implicit dunder returns (e.g. __init__ → None) are always
// excluded from coverage, even when explicitly annotated.
//
// Property setters/deleters have a trivial `-> None` return that
// is not a meaningful typable, so skip it like implicit returns.
// Implicit dunder returns (e.g. __init__ → None) and property setter/deleter
// returns are trivial, so they are excluded even when explicitly annotated.
let skip_return = is_property_deleter
|| matches!(
property_role,
Some(PropertyRole::Setter | PropertyRole::SetterDecorator)
)
|| (fun.class_key.is_some() && is_implicit_dunder_return(fun.def.name.as_str()));
let return_slot = if skip_return {
SlotCounts::default()
let return_rank = if skip_return {
SlotRank::Skip
} else {
SlotRank::classify(return_annotation.is_some(), is_return_type_known).into()
SlotRank::classify(return_annotation.is_some(), is_return_type_known)
};
let mut func_slots = return_slot;
let mut func_slots = SlotCounts::from(return_rank);
let mut n_params = 0usize;
let mut non_self_index = 0usize;

Expand Down Expand Up @@ -862,7 +852,7 @@ fn parse_functions(
functions.push(Function {
name: func_name,
return_annotation,
is_return_type_known,
return_rank,
parameters,
is_type_known,
property_role,
Expand Down Expand Up @@ -901,7 +891,7 @@ fn parse_functions(
location,
range,
return_annotation: target_func.return_annotation.clone(),
is_return_type_known: target_func.is_return_type_known,
return_rank: target_func.return_rank,
parameters: target_func.parameters.clone(),
is_type_known: target_func.is_type_known,
property_role: target_func.property_role.clone(),
Expand Down Expand Up @@ -2302,7 +2292,7 @@ mod tests {
} else {
None
},
is_return_type_known: has_return,
return_rank: SlotRank::classify(has_return, has_return),
parameters: params
.into_iter()
.enumerate()
Expand Down
4 changes: 2 additions & 2 deletions pyrefly/lib/commands/coverage/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ pub struct ReportSuppression {
pub struct Function {
pub name: String,
pub return_annotation: Option<String>,
/// Whether the return type contains no `Any`.
pub is_return_type_known: bool,
#[serde(skip)]
pub return_rank: SlotRank,
pub parameters: Vec<Parameter>,
pub is_type_known: bool,
/// Property role if this function is a property accessor, `None` otherwise.
Expand Down
38 changes: 32 additions & 6 deletions pyrefly/lib/test/coverage/test_files/overloads.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
"names": [
"test.f",
"test.WithOverloads.method",
"test.OverloadedInit.__init__",
"test.OverloadedInit",
"test.WithOverloads"
],
"line_count": 33,
"line_count": 41,
"symbol_reports": [
{
"kind": "function",
Expand All @@ -32,6 +34,30 @@
"column": 5
}
},
{
"kind": "function",
"name": "test.OverloadedInit.__init__",
"n_typable": 1,
"n_typed": 1,
"n_any": 0,
"n_untyped": 0,
"location": {
"line": 36,
"column": 5
}
},
{
"kind": "class",
"name": "test.OverloadedInit",
"n_typable": 0,
"n_typed": 0,
"n_any": 0,
"n_untyped": 0,
"location": {
"line": 35,
"column": 1
}
},
{
"kind": "class",
"name": "test.WithOverloads",
Expand All @@ -46,17 +72,17 @@
}
],
"type_ignores": [],
"n_typable": 4,
"n_typed": 4,
"n_typable": 5,
"n_typed": 5,
"n_any": 0,
"n_untyped": 0,
"coverage": 100.0,
"strict_coverage": 100.0,
"n_functions": 1,
"n_methods": 1,
"n_methods": 2,
"n_function_params": 1,
"n_method_params": 1,
"n_classes": 1,
"n_method_params": 2,
"n_classes": 2,
"n_attrs": 0,
"n_properties": 0,
"n_type_ignores": 0
Expand Down
8 changes: 8 additions & 0 deletions pyrefly/lib/test/coverage/test_files/overloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@ def method(self, x: str) -> str: ...

def method(self, x):
return x


class OverloadedInit:
@overload
def __init__(self, x: int) -> None: ...
@overload
def __init__(self, x: str) -> None: ...
def __init__(self, x): ...
Loading