Treat Protocol bases as ABCMeta-providing when detecting abstract cla…#3683
Open
mikeleppane wants to merge 1 commit into
Open
Treat Protocol bases as ABCMeta-providing when detecting abstract cla…#3683mikeleppane wants to merge 1 commit into
mikeleppane wants to merge 1 commit into
Conversation
…sses A `@final` class inheriting `collections.abc.Sequence` without implementing `__getitem__`/`__len__` was not flagged, though mypy, pyright, and ty all error. In typeshed the small `collections.abc` ABCs (`Reversible`, `Collection`) are `Protocol`s, and `Protocol` is a special form carrying no metaclass, so `extends_abc` never saw the `_ProtocolMeta(ABCMeta)` that makes any protocol subclass an ABC at runtime. The composite `Sequence` was thus misclassified as non-abstract and skipped by the abstract-member machinery, hiding its unimplemented members. Make `extends_abc` treat an `is_protocol()` base as ABCMeta-providing, matching the runtime metaclass. This propagates through the plain intermediate so the final / instantiation / implicit-abstract checks fire correctly. Recognizing `Mapping` as abstract exposed a second issue: every TypedDict is modeled with a synthesized `TypedDictFallback(Mapping, metaclass=ABCMeta)` base that never redeclares Mapping's methods, which would make all TypedDicts look abstract. Short-circuit `calculate_abstract_members` for TypedDicts, which are concrete dicts and never abstract. Fixes facebook#2797
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2797
What
A
@finalclass that inheritscollections.abc.Sequence(orMapping) withoutimplementing its abstract methods is now flagged, matching mypy, pyright, and ty.
Instantiating such a class (
A()) and the opt-in implicit-abstract-classdiagnostic now fire correctly too.
Why
In typeshed the small
collections.abcABCs (Reversible,Collection, …) aredeclared
Protocol, andProtocolis a special form (Protocol: _SpecialForm)that carries no metaclass. pyrefly's
extends_abcheuristic only recognized anabc.ABCbase or anABCMetametaclass, so it never saw the_ProtocolMeta(ABCMeta)that a protocol subclass actually gets at runtime. The composite
Sequence— aplain class whose only bases are those protocols — was therefore misclassified as
non-abstract and skipped, so its
@finalsubclasses inherited no abstractobligation. (Direct protocol bases like
class B(Collection, Reversible)alreadyworked; the gap was one level of indirection away.) mypy, pyright, and ty are the
conformance bar and all three error here.
How
pyrefly/lib/alt/class/class_metadata.rs—extends_abcnow treats anis_protocol()base as ABCMeta-providing (a protocol base ⇒_ProtocolMeta⇒ABCMeta-derived metaclass). This is the whole fix for the reported bug.
calculate_abstract_membersshort-circuits foris_typed_dict()classes. Recognizing
Mappingas abstract otherwise leaked through thesynthesized
TypedDictFallback(Mapping, metaclass=ABCMeta)base and made everyTypedDict look abstract; a TypedDict is a concrete
dictand never is. Caught bymypy_primer on
pandas-stubs, not the unit suite.correct
extends_abc) and the truthiness-redundancy consumer ofextends_abcinexpr.rs(widening it only suppresses more "always true" warnings on abstracttypes — conservative).
Test plan
pyrefly/lib/test/abstract_methods.rs)test_final_class_over_sequence@finaloverSequenceerrorstest_abstract_through_protocol_basesMapping, and a userProtocolreached via a plain intermediatetest_implicit_abstract_through_sequenceSequencesubclassestest_concrete_subclass_of_sequence_is_cleantest_final_class_direct_protocol_bases_still_errorstest_typed_dict_is_not_abstractmypy_primer, wide scope (~37 projects incl.pandas-stubs,pandas,sympy,xarray): zero diff.