Skip to content

Use checkmember.py to check multiple inheritance #18876

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 4, 2025

Conversation

ilevkivskyi
Copy link
Member

This is the third "major" PR towards #7724

This one is mostly straightforward. I tried to preserve the existing logic about mutable overrides (to minimize fallout), as currently we e.g. don't use the covariant mutable override error code here. In future we can separately "synchronize" mutable override logic across variable override, method override, and multiple inheritance code paths (as currently all three are subtly different).

Copy link
Contributor

github-actions bot commented Apr 3, 2025

Diff from mypy_primer, showing the effect of this PR on open source code:

pandas (https://github.com/pandas-dev/pandas)
- pandas/core/series.py:4818: error: Unused "type: ignore" comment  [unused-ignore]
- pandas/core/indexes/range.py:1382: error: Unused "type: ignore" comment  [unused-ignore]

hydpy (https://github.com/hydpy-dev/hydpy)
+ hydpy/models/evap_pet_ambav1.py:382: error: Definition of "prepare_leafareaindex" in base class "Sub_ETModel" is incompatible with definition in base class "PETModel_V2"  [misc]
+ hydpy/models/evap_pet_ambav1.py:382: error: Definition of "prepare_plant" in base class "Sub_ETModel" is incompatible with definition in base class "PETModel_V2"  [misc]
+ hydpy/models/evap_aet_morsim.py:500: error: Definition of "prepare_leafareaindex" in base class "Sub_ETModel" is incompatible with definition in base class "AETModel_V1"  [misc]
+ hydpy/models/evap_aet_morsim.py:500: error: Definition of "prepare_plant" in base class "Sub_ETModel" is incompatible with definition in base class "AETModel_V1"  [misc]
+ hydpy/models/evap_aet_minhas.py:268: error: Definition of "prepare_leafareaindex" in base class "Sub_ETModel" is incompatible with definition in base class "AETModel_V1"  [misc]
+ hydpy/models/evap_aet_minhas.py:268: error: Definition of "prepare_plant" in base class "Sub_ETModel" is incompatible with definition in base class "AETModel_V1"  [misc]
+ hydpy/models/evap_aet_hbv96.py:297: error: Definition of "prepare_leafareaindex" in base class "Sub_ETModel" is incompatible with definition in base class "AETModel_V1"  [misc]
+ hydpy/models/evap_aet_hbv96.py:297: error: Definition of "prepare_plant" in base class "Sub_ETModel" is incompatible with definition in base class "AETModel_V1"  [misc]

spark (https://github.com/apache/spark)
+ python/pyspark/ml/regression.py:1599: error: Definition of "getNumTrees" in base class "_TreeEnsembleModel" is incompatible with definition in base class "_RandomForestParams"  [misc]
+ python/pyspark/ml/classification.py:2271: error: Definition of "getNumTrees" in base class "_TreeEnsembleModel" is incompatible with definition in base class "_RandomForestParams"  [misc]

@ilevkivskyi
Copy link
Member Author

ilevkivskyi commented Apr 3, 2025

New errors in spark are correct, they override method with a property. New errors in hydpy are also techincally correct. The ignore_pos_arg_names flag does not apply to callable instances anymore (they now go through generic code path). I could put some special-casing back, but I would rather not do this, as IIUC long term we are slowly moving away from ignore_pos_arg_names. @tyralla I think you should replace

    def prepare_plant(self, tree: VectorInputBool) -> None: ...

with

    def prepare_plant(self, plant: VectorInputBool) -> None: ...

here https://github.com/hydpy-dev/hydpy/blob/master/hydpy/interfaces/petinterfaces.py#L79, and similarly in few other methods.

@JukkaL I think this is ready for review now.

ok = is_equivalent(first_type, second_type)
if not ok:
second_node = base2[name].node
if second.node is not None and not self.is_writable_attribute(second.node):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This very condition is used as part of the next block - maybe extract to second_is_writeable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are not the same, one has not other doesn't have one.

ok = is_subtype(first_type, second_type)
else:
ok = is_equivalent(first_type, second_type)
if ok:
if (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe merge the ladder? if (ok and ...), nested ifs are no longer necessary here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually intentional, to make it more prominent that we only want to give at most one error here (without a comment).

and self.is_writable_attribute(second.node)
and is_property(first.node)
and isinstance(first.node, Decorator)
and not isinstance(p_second_type, AnyType)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand this bit - why should read-only something be compatible with writable Any? Being writable is somewhat more fundamental than a specific type of the attr IMO. Is this behavior mentioned in the spec?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the type is Any, we can't always know for sure what the attribute is (maybe it is e.g. an alias to unimported property). Also we have a lot of other places where Any affects "arity", including an equivalent check for single inheritance.

(Also TBH I don't really care about the spec, if someone wants me to change something, they need to convince me, not the other way around.)

and second.node
and self.is_writable_attribute(second.node)
and is_property(first.node)
and isinstance(first.node, Decorator)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ough, and this one. A property can be an OverloadedFuncDef and still not represent a writable property, does the following copy of an existing test pass?

[case testMixinTypedPropertyReversedWithDel]
class A:
    @property
    def foo(cls) -> str:
        return "no"
    @foo.deleter
    def foo(cls) -> None: ...
class Mixin:
    foo = "foo"
class C(A, Mixin): # E: Cannot override writeable attribute "foo" in base "Mixin" with read-only property in base "A"
    pass
[builtins fixtures/property.pyi]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this is intentional (see PR description), I didn't want this to be stricter that the equivalent single inheritance check. I have an action item to revisit this after I am done with the core of the refactoring, and "straighten" all the writability checks.

@tyralla
Copy link
Collaborator

tyralla commented Apr 4, 2025

New errors in hydpy are also techincally correct. The ignore_pos_arg_names flag does not apply to callable instances anymore (they now go through generic code path). I could put some special-casing back, but I would rather not do this, as IIUC long term we are slowly moving away from ignore_pos_arg_names. @tyralla I think you should replace

    def prepare_plant(self, tree: VectorInputBool) -> None: ...

with

    def prepare_plant(self, plant: VectorInputBool) -> None: ...

here https://github.com/hydpy-dev/hydpy/blob/master/hydpy/interfaces/petinterfaces.py#L79, and similarly in few other methods.

Yes, these are definitely simple copy-paste errors. I would have expected Mypy to detect them already, given that the "inner" Callable returned by @importtools.define_targetparameter relies on ParamSpec. Performing more keyword argument-related checks when Callable is involved seems generally helpful to me. So, thank you for this improvement!

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, nice to get rid of some duplicate (but inconsistent) logic.

@ilevkivskyi ilevkivskyi merged commit fcabf19 into python:master Apr 4, 2025
18 checks passed
@ilevkivskyi ilevkivskyi deleted the checkmember-multi branch April 4, 2025 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants