-
Notifications
You must be signed in to change notification settings - Fork 160
Check for compatibility when inheriting from multiple classes for fields #1196
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
base: main
Are you sure you want to change the base?
Check for compatibility when inheriting from multiple classes for fields #1196
Conversation
Hey @fangyi-zhou, thanks for working on this! It looks like there are a few broken tests, would you be able to take a look at those? In the meantime, I'll tag @stroxler and @yangdanny97, since they were active on the initial issue. |
This is the tricky part (and hence why I marked this diff as RFC). This change surfaces some issues with standard library in vendored typeshed. Error messages are re-formatted for readability (1) There are some issues with name mismatches in
(2) There are some issues with subtyping generic function (?)
(3) Handling overloads (?)
|
74caf7c
to
6d70077
Compare
Nice work on the typeshed PR! The overall approach makes sense, I'll do a more detailed review soon. Our intersection operation is a bit shaky, so I'm not surprised if there's a bug or two buried there that affects this PR. If it turns out to be very difficult to resolve, we could merge this with the check restricted to TypedDicts (or restricted to non-function types), and iterate till we eventually can run this for everything. |
pyrefly/lib/alt/solve.rs
Outdated
self.error( | ||
errors, | ||
cls.range(), | ||
ErrorInfo::Kind(ErrorKind::InconsistentOverload), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we might need a new error code here
pyrefly/lib/alt/solve.rs
Outdated
cls.range(), | ||
ErrorInfo::Kind(ErrorKind::InconsistentOverload), | ||
format!( | ||
"Inconsistent types for field `{field_name}` inherited from multiple base classes: {class_and_types_str}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can split this into multiple lines,
pyrefly/pyrefly/lib/error/collector.rs
Line 121 in fb552b7
pub fn add(&self, range: TextRange, info: ErrorInfo, mut msg: Vec1<String>) { |
takes a vec1 of lines
pyrefly/lib/alt/solve.rs
Outdated
let mut inherited_fields: SmallMap<&Name, Vec<(&Name, Type)>> = SmallMap::new(); | ||
|
||
for parent_cls in mro.ancestors_no_object().iter() { | ||
let class_fields = parent_cls.class_object().fields(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there some extra work being done here? For example given the class hierarchy
class A:
x: T1
class B:
x: T2
class C(A, B):
x: T3
class D:
x: T4
class E(C, D): ...
Also, when doing the check for E
, we're also checking whether A
and B
's impls are compatible with each other, but that should have already been checked and the error raised in C
's definition, so we'd be raising an extra error.
Maybe that's not a bad thing, it does affect E
as well, but just something to consider.
Given this snippet
class A:
x: int
class B:
x: str
class C(A, B): pass
class D:
x: int
class E(C, D): pass
mypy errors on both C and E, pyright only errors on C
This logic also doesn't seem to account for overrides. For example,
class A:
x: int
class B:
x: str
class C(A, B):
x: int
class D:
x: int
class E(C, D): pass
Here we would check the impl of x from A and B, even though they're both overridden by the impl from C. Mypy only errors on C in this case, but I suspect we would error on E as well.
I wonder if this means we could get away with looking up a single copy of the field from each base class, using the MRO. That would be fewer things to check than looking up every occurrence of that field from anywhere in the class hierarchy.
For the 3rd example you gave
it's from here The parent implementations are definitely not compatible with each other, but as long as the child's overload is compatible with both I think it's fine. I'm not sure what the fix here should be - do we skip methods entirely? (that seems excessive) or could we make an intersection of two incompatible methods generate an overload with both signatures? |
For the second issue you raised, maybe @samwgoldman has an opinion on this. Maybe we need to do some sort of type var substitution (replacing all the parent class's type vars with the child class's type vars so that these checks are all using the same type vars). |
6d70077
to
b7bd774
Compare
I'm made some changes according to the comments, but this stack is not ready for another review yet. I'll investigate another day how to deal with the overloads. |
This seems to be a problem in the standard library. For For Maybe we should exclude constructors from this check? |
If the overloads ends up being tricky or affecting a lot of other behaviors, we could merge a version of this that excludes overloads & implkement that part separately. Re: the constructor stuff, i think that makes sense to skip it. Oftentimes classes will override constructors with completely different signatures, and our override consistency check also skips it. The override consistency check implementation may be a useful reference for what should/should not be skipped for this analysis https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/class/class_field.rs#L1751 |
This diff is an attempt to address the issue in facebook#943. When a class inherits from multiple base classes, we check whether the intersection of the types of the fields is never. If that's the case, we raise an error. Note 1: There are some errors when type checking the builtins (which is reflected by errors in scrut tests). They seem to be caused by subtyping checks of self types in functions and generic function types. Note 2: Should this be a new error category? I'm currently reusing the closest error category for inconsistent overloads, but that looks very specific.
dbfadfe
to
9e00839
Compare
Let's get the field check merged in first, and deal with methods in a follow up diff. |
This diff is an attempt to address the issue in #943.
When a class inherits from multiple base classes, we check whether the intersection of the field types is
never
. If so, we raise an error.Next step: Handle method overrides / overloads.