Add type annotations for rings and parents#41232
Conversation
c7768dd to
fab7698
Compare
fab7698 to
315aa5c
Compare
vincentmacri
left a comment
There was a problem hiding this comment.
Good overall. Just a few questions to make sure I understand this right.
My biggest comment is more of a question. I believe type checkers try to enforce some kind of Liskov substitution principle stuff, which would mean that annotations like def __hash__(self) -> int: are redundant if that annotation is already present in SageObject. So some of the type annotations would be redundant if I'm correct about that.
|
|
||
| def __init__(self, parent: Any, codomain: Parent[Any] | None = ...) -> None: ... | ||
| def __copy__(self) -> Self: ... | ||
| def parent(self) -> Any: ... |
There was a problem hiding this comment.
Why isn't this annotated to return Parent?
There was a problem hiding this comment.
If I understand the code correctly, its actually always a Homset. I've updated the code now to reflect this.
| DomainElementT_contra = TypeVar("DomainElementT_contra", contravariant=True) | ||
| CodomainElementT_co = TypeVar("CodomainElementT_co", covariant=True) |
There was a problem hiding this comment.
I've seen contravariant and covariant in the Python docs before but haven't really seen them used. For the sake of helping me review this, could you briefly explain what these are doing?
There was a problem hiding this comment.
This specifies what concrete types can be substituted, with
- Covariance means that a type can be substituted with a more specific type
- Contravariance means that a type can be substituted with a more general type
So if you specify Map[Animal, Animal], then for an implementation to be substitutable:
- It must accept at least all Animal keys (contravariance)
- It must return at most Animal values (covariance)
There was a problem hiding this comment.
That being said, since we now use Python 3.12 we can use the new Generic syntax which doesn't need this variance anymore.
| def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: ... | ||
| def _repr_type(self) -> str: ... | ||
| def _repr_defn(self) -> str: ... | ||
| def _repr_(self) -> str: ... |
There was a problem hiding this comment.
Shouldn't the type annotation for _repr_ (and other methods like __hash__) be inherited from SageObject? I though type checkers enforced that through the Liskov substitution principle (at least some do), which would mean that this annotation is redundant.
If I'm correct about the behaviour of type annotations with inheritance, that would mean that we would get the most value for the amount of work by annotating classes higher up in the inheritance hierarchy (like you've done in this PR).
There was a problem hiding this comment.
Shouldn't the type annotation for
_repr_(and other methods like__hash__) be inherited fromSageObject? I though type checkers enforced that through the Liskov substitution principle (at least some do), which would mean that this annotation is redundant.
I was playing around with adding annotations to Integer and Element and at least mypy does indeed give an error if the annotations for Integer conflict with the annotations for Element. I didn't check for pyright but I assume it's the same. So I think it should suffice to only annotate methods like _repr_ in SageObject, you don't need to duplicate the annotation (or I think include it in the .pyi file at all if it's already in SageObject. The type checker will be aware of the existing annotation in the superclass.
You can use @override if you want to be more specific about types in a subclass.
There was a problem hiding this comment.
That's correct, but there is currently no typing info for Element. I've added @override now, which is good practice anyway (typing checkers will complain if the type in the subclass is different or the method doesn't exist in the parent). Thanks!
There was a problem hiding this comment.
That's correct, but there is currently no typing info for
Element.
To avoid potential duplicate efforts, just a heads up that I'm working on adding typing info for Element (vincentmacri#22 if you're curious, but it is not remotely ready yet, I'm still experimenting with type checker behaviour and how to get type checking to interact properly with the coercion system).
There was a problem hiding this comment.
To avoid potential duplicate efforts, just a heads up that I'm working on adding typing info for
Element(vincentmacri#22 if you're curious, but it is not remotely ready yet, I'm still experimenting with type checker behaviour and how to get type checking to interact properly with the coercion system).
Typing for elements seems to kind of depend on this PR. Between mypy, pyright, and ty, only mypy is able to properly handle when a correctly-typed file interacts with an incorrectly-typed file.
|
note that "ParentWithGens" is something we should get rid of. Efforts in this direction are currentl blocked at the ticket about moving "fraction_field" method #40213 |
|
Documentation preview for this PR (built with commit c2cc5e7; changes) is ready! 🎉 |
|
@vincentmacri Thanks for the review, and sorry that it took me so long to come back to you (here and in other PRs). I hope I've addressed all your remarks now. |
vincentmacri
left a comment
There was a problem hiding this comment.
I noticed a few recurring issues that I want to ask about before I finish reviewing this.
- Is there a reason why you use
...for the default value of methods that have an explicit default value in the implementation? - I was under the impression that type stub files should only list Python-accessible functions. For Cython code, this means that
cdeffunctions/methods should not be included in the type stub file, but you seem to have included at least a fewcdeffunctions/methods (there might be more that I didn't notice than just the ones I flagged). Am I misunderstanding type stub files or were these cdef functions/methods included in the stub files by mistake?
| names: NameSpec = ..., | ||
| normalize: bool = ..., | ||
| category: Category | None = ..., |
There was a problem hiding this comment.
| names: NameSpec = ..., | |
| normalize: bool = ..., | |
| category: Category | None = ..., | |
| names: NameSpec = None, | |
| normalize: bool = True, | |
| category: Category | None = None, |
These are the actual defaults used in the implementation file. Is there a reason not to use the actual defaults here?
There was a problem hiding this comment.
I thought pyi files should not contain default values, but you and python/typeshed#8988 proved me wrong. Thanks!
| names: NameSpec = ..., | ||
| normalize: bool = ..., | ||
| category: Category | None = ..., |
There was a problem hiding this comment.
| names: NameSpec = ..., | |
| normalize: bool = ..., | |
| category: Category | None = ..., | |
| names: NameSpec = None, | |
| normalize: bool = True, | |
| category: Category | None = None, |
Same as above.
| name: str | None = ..., | ||
| names: NameSpec = ..., |
There was a problem hiding this comment.
| name: str | None = ..., | |
| names: NameSpec = ..., | |
| name: str | None = None, | |
| names: NameSpec = None, |
| def is_Integer(x: Any) -> bool: ... | ||
| def is_Parent(x: Any) -> bool: ... | ||
| def good_as_coerce_domain(S: Any) -> bool: ... | ||
| def good_as_convert_domain(S: Any) -> bool: ... |
There was a problem hiding this comment.
| def is_Integer(x: Any) -> bool: ... | |
| def is_Parent(x: Any) -> bool: ... | |
| def good_as_coerce_domain(S: Any) -> bool: ... | |
| def good_as_convert_domain(S: Any) -> bool: ... | |
| def is_Parent(x: Any) -> bool: ... |
I thought only Python-accessible names go in the type stub files? Aren't the functions is_Integer, good_as_coerce_domain, and good_as_convert_domain all cdef functions that can't be called from Python-space?
There was a problem hiding this comment.
Should be fixed now, thanks!
| names: NameSpec = ..., | ||
| normalize: bool = ..., |
There was a problem hiding this comment.
| names: NameSpec = ..., | |
| normalize: bool = ..., | |
| names: NameSpec = None, | |
| normalize: bool = True, |
| def category(self) -> Category: ... | ||
| def _test_category(self, **options: Any) -> None: ... | ||
| def _test_eq(self, **options: Any) -> None: ... | ||
| def init_coerce(self, warn: bool = ...) -> None: ... |
There was a problem hiding this comment.
| def init_coerce(self, warn: bool = ...) -> None: ... |
This is a cdef method.
No worries! I've had plenty of other work to do lately as well. |
|
I think this is |
| from sage.structure.unique_representation import UniqueRepresentation | ||
|
|
||
| _cache: TripleDict[SageObject, SageObject, Category | None, "Homset"] = TripleDict(weak_values=True) | ||
| _cache: "TripleDict[SageObject, SageObject, Category | None, Homset]" = TripleDict(weak_values=True) |
There was a problem hiding this comment.
Add from __future__ import annotations to the top of the file and you can remove the quotes here.
| def __mul__(self, right: Map[Any, Any]) -> Map[Any, Any]: ... | ||
| def _composition(self, right: Map[Any, Any]) -> Map[Any, Any]: ... | ||
| def _composition_(self, right: Map[Any, Any], homset: Any) -> Map[Any, Any]: ... |
There was a problem hiding this comment.
Not necessary for this PR, just pointing out that these functions would be could candidates for overloads utilizing generics in the future, i.e. if
|
There are a couple suggestions I made for default values in Thanks for the contribution! Once this is merged I think I can finish the type annotations for |
Co-authored-by: Vincent Macri <vincent.macri@ucalgary.ca>
|
Test failure seems irrelevant. |
|
Thanks! |
sagemathgh-41232: Add type annotations for rings and parents <!-- ^ Please provide a concise and informative title. --> <!-- ^ Don't put issue numbers in the title, do this in the PR description below. --> <!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method to calculate 1 + 2". --> <!-- v Describe your changes below in detail. --> <!-- v Why is this change required? What problem does it solve? --> <!-- v If this PR resolves an open issue, please link to it here. For example, "Fixes sagemath#12345". --> Add a bit more typing annotations/stubs for a few fundamental objects (with the goal to make typing of the higher level Python interface easier). They are not meant to be 100% accurate and contain a few to many `Any`s for my taste, but it should serve as a good start. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [ ] The title is concise and informative. - [ ] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [ ] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - sagemath#12345: short description why this is a dependency --> <!-- - sagemath#34567: ... --> URL: sagemath#41232 Reported by: Tobias Diez Reviewer(s): Tobias Diez, Vincent Macri
sagemathgh-41232: Add type annotations for rings and parents <!-- ^ Please provide a concise and informative title. --> <!-- ^ Don't put issue numbers in the title, do this in the PR description below. --> <!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method to calculate 1 + 2". --> <!-- v Describe your changes below in detail. --> <!-- v Why is this change required? What problem does it solve? --> <!-- v If this PR resolves an open issue, please link to it here. For example, "Fixes sagemath#12345". --> Add a bit more typing annotations/stubs for a few fundamental objects (with the goal to make typing of the higher level Python interface easier). They are not meant to be 100% accurate and contain a few to many `Any`s for my taste, but it should serve as a good start. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [ ] The title is concise and informative. - [ ] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [ ] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - sagemath#12345: short description why this is a dependency --> <!-- - sagemath#34567: ... --> URL: sagemath#41232 Reported by: Tobias Diez Reviewer(s): Tobias Diez, Vincent Macri
Add a bit more typing annotations/stubs for a few fundamental objects (with the goal to make typing of the higher level Python interface easier). They are not meant to be 100% accurate and contain a few to many
Anys for my taste, but it should serve as a good start.📝 Checklist
⌛ Dependencies