Skip to content
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

False positive IncompatibleMethodOveride error #867

Closed
russelldavis opened this issue Jul 21, 2020 · 9 comments
Closed

False positive IncompatibleMethodOveride error #867

russelldavis opened this issue Jul 21, 2020 · 9 comments
Labels
addressed in next version Issue is fixed and will appear in next published version enhancement request New feature or request

Comments

@russelldavis
Copy link

class Parent:
    def foo(self): ...

class Child(Parent):
    def foo(self, _arg=1): ...

Pyright reports "Parameter count mismatch", but since the extra parameter in Child has a default value, the method is still compatible with the number of parameters in Parent.

@erictraut
Copy link
Collaborator

This behavior is intended. Subclasses that override methods in a base class should have the same signature as the base class. This is a general principle in object-oriented design. If you disagree with this principle, you can choose to quiet this diagnostic rule.

@erictraut erictraut added the as designed Not a bug, working as intended label Jul 21, 2020
@russelldavis
Copy link
Author

This is a general principle in object-oriented design

It really isn't. What is definitely a general OO principle is Liskov substitution[1], and by that principle it's a false positive. Typescript agrees, BTW.

[1] https://en.wikipedia.org/wiki/SOLID

@russelldavis
Copy link
Author

russelldavis commented Jul 21, 2020

Another way to look at it: functions with default parameters are isomorphic with wrapper functions that forward on the defaults. So, let's say I rewrite the example this like this:

class Parent:
    def foo(self): ...

class Child(Parent):
    def foo(self):
        return self._foo(1)

    def _foo(self, _arg): ...

Now it's in compliance with your principle. What did that actually accomplish?

@erictraut erictraut removed the as designed Not a bug, working as intended label Jul 22, 2020
@erictraut
Copy link
Collaborator

Liskov substitution requires that objects in a hierarchy are substitutable. If you introduce a new (optional) parameter and someone provides an argument for that parameter, the target object can no longer be substituted for other objects in the hierarchy because they don't know about that argument.

The code you provided above is exactly how I would implement this because it doesn't result in this problem. As a small variation on your code, you could name "_foo" something like "foo_with_option" so callers who happen to know that it's a Child object can safely call that method.

Let me consult with a few more people on this topic before making a final decision. I'm of two minds on this one.

@russelldavis
Copy link
Author

If you introduce a new (optional) parameter and someone provides an argument for that parameter, the target object can no longer be substituted for other objects in the hierarchy because they don't know about that argument.

I'm not seeing how the default arg version could break substitutability in a way that wouldn't also happen in the equivalent foo_with_option version. Mind posting a code snippet that shows it?

@erictraut
Copy link
Collaborator

I had a chance to discuss this today with a couple of my colleagues on the TypeScript team. They said this falls into a gray area, but they generally agreed with the argument you made above. They came to the same conclusion when they considered this point for TypeScript. Other typed languages, such as C# and C++, have come down differently on this topic.

I'll need to think about this more. Python's support for named parameters (including parameters that can be specified either positionally or named), *vargs, and **kwargs makes it tricky to come up with the right set of rules for when a subclass signature is truly a safe override. The logic that's currently implemented in Pyright checks for a match in arity (i.e. the parameter count), parameter names, and parameter types (although it allows for narrower types in the override). I'll need to think about a new set of (more complicated) rules that still guarantees safety.

@erictraut
Copy link
Collaborator

I've extended the logic for the "reportIncompatibleMethodOverride" rule. It now knows about postition-only parameters and allows a subclass to extend the signature as long as the extension includes only *vargs, **kwargs, and parameters with default values.

@erictraut erictraut added the addressed in next version Issue is fixed and will appear in next published version label Jul 26, 2020
@erictraut
Copy link
Collaborator

This is addressed in Pyright version 1.1.57, which I just published. It will also be included in the next version of Pylance.

@alexkoay
Copy link

Closes #682 as well, thank you @russelldavis for getting this through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addressed in next version Issue is fixed and will appear in next published version enhancement request New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants