diff --git a/pyrefly/lib/commands/coverage/collect.rs b/pyrefly/lib/commands/coverage/collect.rs index d958b2f83b..46dad1eb01 100644 --- a/pyrefly/lib/commands/coverage/collect.rs +++ b/pyrefly/lib/commands/coverage/collect.rs @@ -740,11 +740,12 @@ fn parse_functions( } } } else { - // Keep only public, exported, non-deleted module-level functions. + // Keep only public, exported, non-deleted, non-excluded-dunder module-level functions. if !exports.contains_key(&fun.def.name.id) || (!is_public_name(fun.def.name.as_str()) && !dunder_all.contains(&fun.def.name.id)) || deleted.contains(&fun.def.name.id) + || EXCLUDED_MODULE_DUNDERS.contains(&fun.def.name.as_str()) { continue; } @@ -2554,6 +2555,13 @@ mod tests { compare_snapshot("private_in_all.expected.json", &report); } + /// PEP 562 module hooks written as `def` are excluded from the report (issue #4020). + #[test] + fn test_report_module_dunder_hooks() { + let report = build_module_report_for_test("module_dunder_hooks.py"); + compare_snapshot("module_dunder_hooks.expected.json", &report); + } + /// CPython-injected module globals are excluded even when control flow /// wraps them in Phi bindings (issue #3505). #[test] diff --git a/pyrefly/lib/test/coverage/test_files/module_dunder_hooks.expected.json b/pyrefly/lib/test/coverage/test_files/module_dunder_hooks.expected.json new file mode 100644 index 0000000000..f4b1d8ab11 --- /dev/null +++ b/pyrefly/lib/test/coverage/test_files/module_dunder_hooks.expected.json @@ -0,0 +1,37 @@ +{ + "name": "test", + "path": "test.py", + "names": [ + "test.visible" + ], + "line_count": 20, + "symbol_reports": [ + { + "kind": "function", + "name": "test.visible", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 18, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 100.0, + "n_functions": 1, + "n_methods": 0, + "n_function_params": 0, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 +} diff --git a/pyrefly/lib/test/coverage/test_files/module_dunder_hooks.py b/pyrefly/lib/test/coverage/test_files/module_dunder_hooks.py new file mode 100644 index 0000000000..ec51cfaa66 --- /dev/null +++ b/pyrefly/lib/test/coverage/test_files/module_dunder_hooks.py @@ -0,0 +1,19 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# Regression test for gh-4020: PEP 562 module hooks written as `def` must be +# excluded from coverage, like the assignment form already is. + + +def __getattr__(name): + return None + + +def __dir__(): + return [] + + +def visible() -> int: + return 1