-
Notifications
You must be signed in to change notification settings - Fork 211
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
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. |
|
@yangdanny97 has imported this pull request. If you are a Meta employee, you can view this in D84155735. |
| let types: Vec<Type> = class_and_types.iter().map(|(_, ty)| ty.clone()).collect(); | ||
| if types | ||
| .iter() | ||
| .any(|ty| matches!(ty, Type::BoundMethod(..) | Type::Function(..))) |
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.
Probably also want Type::Overload here; and possibly Type::ForAll for generic functions.
I can patch the PR after import since it's a small change
stroxler
left a comment
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.
Review automatically exported from Phabricator review in Meta.
|
@yangdanny97 merged this pull request in 5144e69. |
Summary: This PR is a series of minor diffs building up to the correct handling of multiple inheritance checks for methods, following up from #1196. Main changes: - [Skip multiple inheritance check for fields that are overridden](6fccf4a): this diff prevents repeated error reporting when a field gets overridden -- in those cases we don't report inconsistent multiple inheritance - [Skip multiple inheritance checks for "special" fields](b290fd1): this diff skips checks for multiple inheritances for some special fields like constructors. - Cherry picking fixes for some problematic function types in stdlib in vendored typeshed Next steps: We're close to handle multiple inheritance checks for methods. We still have some errors on stdlib due to handling of type variables (#1196 (comment) item 2), which I consider as a blocker. Pull Request resolved: #1298 Reviewed By: kinto0 Differential Revision: D85053799 Pulled By: yangdanny97 fbshipit-source-id: 2e6c22916886dc4c760931cf49522a057c7b42aa
Follow up from facebook#1196. Closes facebook#943. When getting the field type from the parent field, we now check for instance method types first before using the raw types. When using the raw types, the type parameters are not initialised, hence causing issues with functions that use type variables during subtyping checks. This diff adds a regression test for these cases.
Summary: Follow up from #1196. Closes #943. When getting the field type from the parent field, we now check for instance method types first before using the raw types. When using the raw types, the type parameters are not initialised, hence causing issues with functions that use type variables during subtyping checks. This diff adds a regression test for these cases. Pull Request resolved: #1452 Reviewed By: stroxler Differential Revision: D86374052 Pulled By: yangdanny97 fbshipit-source-id: 32bb6f9cdfcedcde0a6c310804ddbb6df4be18bb
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.