Close the gaps in the IcePy stub/module consistency check - #6609
Conversation
There was a problem hiding this comment.
Pull request overview
Extends IcePy’s consistency checker to detect missing declarations, attributes, duplicate definitions, and documentation drift.
Changes:
- Adds bidirectional module/stub validation and attribute checks.
- Adds reflow-tolerant prose comparison.
- Documents nine previously bare stub functions.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
scripts/checkIcePyStub.py |
Expands consistency validation. |
python/python/IcePy-stubs/__init__.pyi |
Adds missing function documentation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
6f6d91b to
1a94d5a
Compare
externl
left a comment
There was a problem hiding this comment.
shipped()dereferencesobj.__mro__unguarded (checkIcePyStub.py:137), so a stubclasswhose module counterpart is a function crashes with anAttributeErrortraceback instead of a diagnostic —reverseProblemsguards the identical case at:199.- A read-only
@propertyin the stub is rejected:signatureOfrenders anydefasname(...)and then requires the C docstring to open with it, which a getset descriptor never does. TheEndpointInfogetsets really are read-only, so the natural stub correction is blocked by the check. - A duplicate def is reported and still half-compared against the last definition (the docstring at
:68says otherwise), so an@overloadpair produces a spurious second error. - The printed diff is the raw text while the comparison is normalized, so a real divergence in a rewrapped docstring gets buried under wrap-only noise; diffing the normalized strings would fix it.
|
Thanks for the review. Addressed all four points in
I also added focused regression coverage and reran Ruff, Pyright, and the IcePy stub consistency check successfully. |
externl
left a comment
There was a problem hiding this comment.
All four earlier points are fixed — I rebuilt IcePy and exercised each one. Two new things came out of that:
normalizeProseregressed reflow tolerance.LIST_ITEM = (?:[-+*]|#\.|\d+[.)])\smatches any wrapped line beginning with digits-then-period, so it refuses to fold it as a continuation.intVersion's docstring contains "…the returned value is 30901. For pre-releases…"; today the wrap happens to fall after30901., but moving that wrap point — without changing a single word — producesintVersion: descriptions differ. That's the opposite of headline claim 2.- The
@propertyfix trades a false rejection for a false acceptance.isPropertypassesNoneas the stub signature (:113), so both signature-comparison branches are skipped entirely rather than checked differently. MarkingEndpointInfo.typeas a@propertywhile IcePy shipstype() -> intis now silently green, and pyright would then rejectinfo.type(). Requiring thatsplit()found no signature line whenisPropertywould keep the fix without the hole.
Also worth a pass: the description still covers only the first commit, so @property support, the normalized diff output, the duplicate suppression, the __mro__ guard and the normalizeProse rewrite are all undocumented.
For what it's worth, nothing became permissive — stub-only names, module-only names, arity drift, deleted docstrings and semantic edits inside attribute docstrings all still fire, and a reflow-only rewrap still passes.
|
Both points fixed in
|
There was a problem hiding this comment.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Note
This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.
| continue | ||
|
|
||
| continuation = indent == joinIndent | ||
| underline = not stripped.strip("-=~^\"'`#*+_") |
There was a problem hiding this comment.
Fixed in b29a33c343. A section adornment is now recognized by its shape — a single punctuation character repeated (ADORNMENT) — instead of a hand-picked character set, so Heading / :::::: stays structural.
| pythonSupplied = ( | ||
| value is None | ||
| and member == "__hash__" | ||
| or isinstance(value, types.WrapperDescriptorType) |
There was a problem hiding this comment.
Fixed in b29a33c343. You are right that the four __init__ slots were being swallowed — Properties, Communicator, ObjectPrx and Operation are all wrapper_descriptor, so their stub signatures were unchecked, leaving item 4 of #6443 open.
Rather than exempting less, the module now provides the missing source: those four types spell their constructor in tp_doc, the way a C type documents one, and the check holds the stub's own __init__ declaration to that line. A wrong Properties.__init__ signature is now reported as Properties: signatures differ. None of the four is re-exported by Ice/IcePyTypes.py, so no published page changes.
externl
left a comment
There was a problem hiding this comment.
A few gaps still open, all in scripts/checkIcePyStub.py:
if pythonSupplied: continue(line 310) drops every__init__before signature comparison, so item 4 of #6443 stays open — a wrongProperties.__init__signature keeps the check green.topLevel(line 128) only suppresses, never verifies. A module constant the stub invents is never checked to exist, andLogger: Anyin place ofclass Logger:silently drops all its member comparisons.- Attribute annotations are compared to nothing (line 122) — only the docstring is.
underlying: EndpointInfo | Noneatpython/python/IcePy-stubs/__init__.pyi:384already disagrees with its own docstring and the getset doc, and the check passes. joinIndentis cleared after a structural line (line 249), so rewrapping a bullet's wording reportsdescriptions differwith no word changed.
🤖 Reviewed with Claude Code
|
All four fixed in
Copilot's two also landed: the section-adornment set is now recognized by shape, so The mutation battery in the description is the current one — 13 red cases, 4 green, including bullet-rewrap and |
The checker now compares the stub's 26 annotated attributes against the module's getset docstrings, tolerates paragraph reflow (equal-indent lines join; blank lines, indentation changes, and section underlines still have to match), reports descriptions the module ships that the stub lacks, reports duplicate stub declarations instead of half-comparing them, exempts only documentation CPython itself generates (slot wrappers, members inherited from object, the __hash__ = None of an unhashable type) instead of every dunder by name, and sweeps the module's own inventory in reverse so a public member the stub omits is reported. The reverse-prose check surfaced nine top-level functions the module documents but the stub did not; their docstrings are copied into the stub.
normalizeProse treated any line opening with a list, field, or directive marker as structural, including one that was only a wrapped continuation. intVersion's "...the returned value is 30901. For pre-releases..." would read as an enumerated item the moment the wrap moved, reporting drift for a paragraph whose words had not changed. A marker now counts only where reST would begin a block -- after a blank line, or at a new indentation -- while a section underline still attaches to the line above it. Declaring a stub member @Property left its signature None, which skipped both signature comparisons rather than changing them. A stub could mark a member an attribute while IcePy documented it as callable, and the check stayed green even though pyright would then reject the call. Properties and annotated attributes now carry a flag, and IcePy shipping a signature line for either is reported. Claude-Session: https://claude.ai/code/session_0164LsXFe8qNNDfchEUjoCCN
Four gaps were left, and closing the first one needs the module's help. A __init__ slot carries the wrapper CPython generates, whose docstring describes object's generic one, so exempting it as CPython-supplied left the signatures the stub declares for Communicator, ObjectPrx, Operation and Properties unverified -- item 4 of #6443, which this claimed to close. Those four types now spell their constructor in tp_doc, the way a C type documents a constructor, and the check holds the stub's __init__ to that line. None of the four is re-exported by Ice, so nothing published moves. A name declared at module scope only suppressed the reverse sweep, so the stub could invent a constant that no module attribute backs. Those names now go through the same existence check as everything else, TypeVar and its kind excepted, since they exist for the type checker alone. Standing a value where IcePy defines a class -- `Logger: Any` -- is reported too: it type-checks anything, and takes every member of the class out of the comparison with it. An attribute's annotation was compared to nothing. The C sources open a data member's description by naming its type, so the two are now held to each other, modulo the module a type is named through: Ice re-exports IcePy's C types unchanged, so Ice.EndpointInfo and EndpointInfo are the same class and both spellings appear. Finally, normalizeProse folds a list item's own wrapping, so rewrapping a bullet is no longer drift, and recognizes a section adornment by its shape rather than a hand-picked character set that missed "::::::". Claude-Session: https://claude.ai/code/session_0164LsXFe8qNNDfchEUjoCCN
b29a33c to
8ab72ee
Compare
Closes #6443.
Closes all six gaps recorded there, in
scripts/checkIcePyStub.py, plus nine docstring additions to the stub and four constructor docstrings in the C sources:normalizeProse()folds hard line wrapping, so rewrapping a paragraph or a bullet does not read as drift, while blank lines, indentation changes, lists, and section underlines still have to match. The two wrap-only attribute divergences the issue found pass without any stub or C++ edit.stringVersion,intVersion,createProperties,stringToIdentity,identityToString,getProcessLogger,setProcessLogger,loadSlice, andcompileSliceare documented in the module but were bare in the stub; their docstrings are copied into the stub. Atp_docof"IcePy.<name>"— the C sources' placeholder for an undocumented class — counts as no description.__init__slot carries the wrapper CPython generates, and its docstring describesobject's generic one, so there was nothing in the module to hold the stub to.Communicator,ObjectPrx,OperationandPropertiesnow spell their constructor intp_doc, the way a C type documents one, and the check compares the stub's own__init__declaration against that line. None of the four is re-exported byIce/IcePyTypes.py, so no published page changes.vars(IcePy)must be declared by the stub, and every member invars()of each class must be declared by the stub class or one of its stub bases. Dunders thatvars(object)supplies need not be repeated, but a slotobjectdoes not have —ExecutorCall.__call__— is required, since omitting it would make the type checker reject a legitimate call.Review follow-ups
Three rounds of review are folded in, in
8c0f0ab7f7,e368ddc874andb29a33c343:Noneis CPython's only for__hash__— the exemption no longer swallows everyNone-valued class member.normalizeProse()folds prose only — list items, field lists, directives, section underlines, and indented literal or preformatted blocks keep their own lines, soloadSlice's five option bullets are not collapsed into one paragraph. A marker counts only where reST would begin a block — after a blank line, or at a new indentation — so a wrapped continuation that merely opens with one (30901. For pre-releases…) stays part of its paragraph. A list item's own wrapping folds, since its text continues on the following lines indented past the marker; a nested list is still read as a list, and a field list or directive is left alone because what follows it is an indented body, not the same sentence carrying on. A section adornment is recognized by its shape — a repeated punctuation character — rather than a hand-picked set that missed::::::.__mro__dereference guarded — a stubclasswhose module counterpart is a function is reported as a difference instead of crashing with anAttributeErrortraceback.@propertyaccepted, and still checked — a read-only getset can be declared@propertywithout the check demanding a callable signature from IcePy, and a property or attribute whose IcePy docstring does open with a signature line is reported, since the stub would otherwise promise an attribute for something the caller has to call.TypeVarand its kind excepted, since they are there for the type checker alone), and standing a value where IcePy defines a class —Logger: Any— is reported, because it type-checks anything and takes every member of that class out of the comparison with it.Ice.EndpointInfoisIcePy.EndpointInfo, and the C sources use both spellings.@overloadpair no longer also produces a spurious mismatch against its last definition.Verification
Each branch was red/green probed against a fresh IcePy build. The mutation battery, run against the current head:
EndpointInfo.type: descriptions differEndpointInfo.compress: descriptions differEndpointInfo.compress: IcePy ships a description, but the stub does not document itloadSlice: signatures differProperties.__init__signatureProperties: signatures differNotInIcePy: declared in the stub, but IcePy does not define itINVENTED_CONSTANT: declared in the stub, but IcePy does not define itLogger: AnyLogger: IcePy defines a class, but the stub declares it as a valueEndpointInfo.datagram: IcePy defines it, but the stub does not declare itintVersion: the stub declares it more than once@propertyover a callableEndpointInfo.type: the stub declares it as a value, but IcePy documents it as callableEndpointInfo.underlying: the stub's type and the one IcePy documents differEndpointInfo.compress: IcePy's description does not open by naming the type@propertyIce.EndpointInfovsEndpointInfoRuff, clang-format, Pyright, and the checker itself are green against a fresh build; Pyright reports no new diagnostic.
No changelog fragment, matching #6426 (tooling; no user-facing behavior change).