Skip to content

fix(#971): OCCTDocumentIsLabelModified names the right attribute, and the other line was the wrong one - #984

Merged
gsdali merged 4 commits into
mainfrom
fix/971-islabelmodified-comment
Aug 20, 2026
Merged

fix(#971): OCCTDocumentIsLabelModified names the right attribute, and the other line was the wrong one#984
gsdali merged 4 commits into
mainfrom
fix/971-islabelmodified-comment

Conversation

@gsdali

@gsdali gsdali commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

What & why

OCCTDocumentIsLabelModified's header comment contradicted itself across two lines: the first
attributed the function to TDocStd_Modified on the root label, the second said it uses
TDocStd_Document::GetModified() and "not TDocStd_Modified attribute directly". Only one could
stay.

#971 picked the wrong one, and this PR keeps the other. The issue reasoned that
TDocStd_Modified is never constructed anywhere in Sources/OCCTBridge (true, its header is
#included once and the class never named) and concluded that GetModified() therefore returns
"the document's own TDF_LabelMap, which is a different mechanism from the root-label
TDocStd_Modified attribute". Measured, that last step is wrong. TDocStd_Document has no map of
its own; all three of its modified-label methods are forwarders
(TDocStd_Document.cxx:151-175):

void TDocStd_Document::SetModified(const TDF_Label& L) { TDocStd_Modified::Add(L); }
void TDocStd_Document::PurgeModified()                 { TDocStd_Modified::Clear(Main()); }
const NCollection_Map<TDF_Label>& TDocStd_Document::GetModified() const
{ return TDocStd_Modified::Get(Main()); }

and TDocStd_Modified's statics all resolve through label.Root(). The class does not appear in
the bridge because the kernel constructs it on the bridge's behalf inside TDocStd_Modified::Add,
not because the bridge reaches a different store. So the surviving line is the first:

/// Check if a label is marked as modified (via the TDocStd_Modified attribute on the root label).
bool OCCTDocumentIsLabelModified(OCCTDocumentRef doc, int64_t labelId);

Measured with a second construction rather than by reading the source twice.
Scripts/repro/971-islabelmodified-attribution/probe.mm
marks a label through the document API and reads it back through the attribute API, against the
pinned kernel:

before SetModified:
  doc->GetModified() threw             : Standard_DomainError: TDocStd_Modified::Get : IsEmpty
  root has TDocStd_Modified attribute  : no
after doc->SetModified(child):
  root has TDocStd_Modified attribute  : yes
  doc->GetModified().Contains(child)   = true
  attr->Get().Contains(child)          = true
  &doc->GetModified() == &attr->Get()  = true (one map, one mechanism)
after doc->PurgeModified():
  doc->GetModified().Contains(child)   = false
  attr->Get().Contains(child)          = false
after TDocStd_Modified::Add(child):
  doc->GetModified().Contains(child)   = true

The same map object by address, reachable and clearable from both sides. One store, two ways in.

Closes #971

The reformat, and how it was checked

Sources/OCCTBridge/include/OCCTBridge_Document.h is grandfathered on
Scripts/style-manifest-bridge.txt, so touching it obliges this PR to bring it fully
clang-format clean and remove the entry
(okf/policies/code-style.md). The 1,714 lines #971 quotes
reproduce exactly
: 322 lines removed, 610 added, 72 hunks, the file growing 2,233 to 2,521 lines.
The churn is 932 lines; 1,714 is the diff -u total including three context lines per hunk.
clang-format --dry-run --Werror is clean after, and CI's own sweep over all 17 non-manifest
bridge files still passes.

It is a separate commit from the two-line correction (b3c200e then 53d6905) so the content
change can be read without scrolling a thousand lines of whitespace, matching the ordering PR #969
used for Curve2D.swift.

Verifying that a sweep that size changed no code needed more than "clang-format is a formatter".
tokens-unchanged.py compares
the file before and after by token sequence, so formatting is invisible to it and a moved,
added or deleted token is not:

CODE      identical: 51192 chars of normalized non-comment text
COMMENTS  identical after whitespace collapse: 39591 chars

Comparing normalized text would not have been enough, and that is measured rather than assumed:
the first run of this comparator collapsed whitespace runs to a single space and reported
CODE DIFFERS at double *_Nonnull becoming double* _Nonnull, a star moving across a space
rather than a token changing. Tokenizing is what tells those apart.

Prove-the-test-fails

tokens-unchanged.py --self-test runs nine cases and every one was watched failing before it
passed. Four reformat-shaped edits must read as unchanged (whitespace, pointer-star placement,
re-indentation, comment reflow); five content-shaped edits must not (an identifier rename, a
deleted declaration, an added extern, a changed comment word, and a // inside a string
literal). Three of the five were additionally injected into the real header and confirmed
caught before the fixture cases were written: renaming labelId to labelID reported the
divergence at char 505, deleting the OCCTDocumentIsLabelModified declaration outright at char
7,784, and changing one word of /// Clear all modification marks. reported a comment divergence
with the code stream still identical.

Under a one-at-a-time removal matrix no case is decorative: 9/9 baseline, 8/9 without the
tokenizer, 7/9 without comment stripping, 8/9 without string-literal awareness.

Second finding, fixed: a method that does not exist

docs/reference/Document.md:1399 attributed Document.isModified(_:) to
TDocStd_Document::IsModified. That method does not exist in the pinned 8.0.1 kernel: it is
commented out at TDocStd_Document.cxx:158-161 and absent from the header. Corrected to
TDocStd_Document::GetModified, the method the bridge actually calls. One line, called out here
rather than folded in silently.

Scripts/census-doc-occt-attribution.py cannot catch this class of error today: it resolves the
class an attribution names against the pinned headers and never the method, so
TDocStd_Document::IsModified passes because TDocStd_Document exists. Noted, not fixed, since
teaching it method resolution is its own change with its own false-positive budget.

Third finding, not fixed: GetModified() throws on an untouched document

The probe recorded a behaviour nothing documented. TDocStd_Modified::Get raises
Standard_DomainError("TDocStd_Modified::Get : IsEmpty") rather than returning an empty map when
the root carries no attribute, so doc->GetModified() throws on any document where nothing has
ever been marked modified. SetModified() and PurgeModified() both tolerate the absence.

OCCTDocumentIsLabelModified's existing catch (...) { return false; } turns that into the
correct answer, so this is not a defect and no bridge change is made here. It is written up in the
repro README because it is exactly the kind of thing a later "simplify the redundant catch" pass
would remove. It is also why the throw is unreachable from Swift: Document.isModified(_:)
answers false for an untouched document rather than trapping.

Fourth finding, not fixed: PR #977 carries the reversed claim in three places

#977 (fix/810, open) propagates #971's premise into its census artifact and into the docs:

  • Scripts/repro/810-refman-document-xde/refman_census.py, the TDocStd_Modified gap rationale:
    "OCCTDocumentIsLabelModified reads TDocStd_Document::GetModified() instead, which is a
    different mechanism".
  • the same file's DEFERRED_OVER_FINDINGS entry for this function, whose correct: text says the
    same and whose bad_phrase is the line this PR kept, not the line it deleted. That entry is
    documented as failing when the phrase disappears without moving to KNOWN_OVER_FINDINGS, so it
    needs re-pointing either way.
  • the paragraph fix(#810): refman coverage audit for Document/XDE assembly (Pass 3) #977 adds to docs/occtswift-wrapping-gaps.md, same wording.

Flagged on #977 rather than edited across PR branches. Neither file is on main yet, so nothing in
this diff depends on the ordering.

This unblocks PR #980

#980's code-style check is currently failing for exactly the obligation this PR discharges:
it edits OCCTBridge_Document.h (the OCCTDocumentAssemblyItemCount signature, around line 2040)
while leaving the file on the manifest. Once this lands the file is off the manifest, so #980's
rebase only has to keep its own hunk clang-format clean, which it already is, rather than carry a
1,714-line sweep of its own. The rebase will conflict at that hunk, since the reformat moves every
line number in the file; the resolution is to re-apply #980's three-line signature onto the
formatted context.

Notes for the reviewer

  • Sources/OCCTBridge/src/OCCTBridge_Document.mm is deliberately not touched. fix(#964): report the assembly-count bound instead of returning it as the count #980 and Document.openNamedTransaction drops its name, and transactionNumber returns a flag rather than a number #970's
    work are both in it, and the only change it would want is a comment about the catch (...)
    above, which the repro README carries instead.
  • No test is added. The change is a comment, a docs line, and a whitespace sweep; there is no
    behaviour to assert that Document.isModified(_:)'s existing coverage does not already assert.
    The verification that earns the checklist item is the token comparison and its self-test, both
    run with their subject broken.
  • The header is a C declaration file, so the corrected comment is one line, per code-style.md's
    terse-doc-comment rule. The Standard_DomainError behaviour that would have justified a second
    line is in docs/ instead, which is where that policy puts the why.
  • Tests: swift test 5634 tests in 1464 suites, 0 failures, run twice, once against the
    pinned kernel and once with OCCTSWIFT_LOCAL=1 against the locally built one. That is one test
    short of the 5635 quoted to me as the baseline; the difference is not from this branch, since
    git diff --name-only origin/main..HEAD contains no file under Tests/, so the count on
    origin/main is the same 5634 by construction. The suite count matches exactly.
  • Gates: all 20 CI invocations pass, including check-style-manifest.py --base origin/main clean.
    The manifest ratchet was confirmed by measurement rather than read from the docstring: re-adding
    the entry makes the script exit 1 with "still lists 1 file(s) this PR touches", and removing it
    again restores exit 0.

CHANGELOG entry

OCCTDocumentIsLabelModified is documented against the attribute it actually reads (#971)

The bridge header comment on OCCTDocumentIsLabelModified contradicted itself, naming
TDocStd_Modified on one line and denying it on the next. TDocStd_Document::GetModified() is a
forwarder to TDocStd_Modified::Get(Main()), so the attribute on the root label is the mechanism
and the denial was the wrong half; the comment now says so once. docs/reference/Document.md
separately attributed Document.isModified(_:) to TDocStd_Document::IsModified, a method that
does not exist in OCCT 8.0.1, and now names TDocStd_Document::GetModified.

Sources/OCCTBridge/include/OCCTBridge_Document.h comes off
Scripts/style-manifest-bridge.txt in the same change, per the code-style rollout's
fix-what-you-touch rule. No behaviour change: the reformat was verified to leave the file's token
sequence byte-identical.

SemVer impact

NONE. A comment, a documentation line, and a whitespace reformat. No declaration, signature,
symbol or behaviour changes; the reformat's token sequence is byte-identical before and after.

gsdali and others added 3 commits August 20, 2026 10:09
…ribute

The two-line header comment on OCCTDocumentIsLabelModified contradicted itself:
line 1 attributed the function to "TDocStd_Modified on root", line 2 said it
uses TDocStd_Document::GetModified() "not TDocStd_Modified attribute directly".

#971 proposed keeping line 2, on the reasoning that TDocStd_Modified is never
constructed anywhere in Sources/OCCTBridge (true) and that GetModified()
therefore returns a document-owned map, "a different mechanism from the
root-label TDocStd_Modified attribute". Measured, that last step is wrong and
the correction runs the other way: line 1 was right, line 2 is the one deleted.

TDocStd_Document::GetModified() is a three-line forwarder returning
TDocStd_Modified::Get(Main()), and TDocStd_Modified's statics all resolve
through label.Root(). TDocStd_Document owns no map of its own; it has no
myModified member, and TDocStd_Document::IsModified does not exist in 8.0.1 at
all (commented out in TDocStd_Document.cxx). The class never appears in the
bridge because the kernel constructs it on the bridge's behalf inside
TDocStd_Modified::Add, not because the bridge reaches a different store.

Measured with a second construction rather than by reading the source twice:
Scripts/repro/971-islabelmodified-attribution/probe.mm marks a label through the
document API and reads it back through the attribute API. The root gains a
TDocStd_Modified attribute, the map GetModified() returns is the same object by
address as the root attribute's own Get(), PurgeModified() clears it, and
TDocStd_Modified::Add is visible through GetModified(). One store, two ways in.

docs/reference/Document.md attributed isModified(_:) to
TDocStd_Document::IsModified, the method that does not exist; corrected to
TDocStd_Document::GetModified, the one the bridge calls.

The probe also records a behaviour nothing documented: GetModified() throws
Standard_DomainError on a document where nothing has been marked, because
TDocStd_Modified::Get raises rather than returning an empty map. The bridge's
existing catch (...) returns false, which is the correct answer, so no bridge
change is needed.

Closes #971

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…, off the manifest

okf/policies/code-style.md's manifest rule: touching a grandfathered file
obliges the same PR to make it clean and remove its entry. Measured against
Sources/OCCTBridge/.clang-format, that is 1,714 lines of diff -u output for
this header, matching #971's own figure: 322 removed, 610 added, 72 hunks, the
file growing 2,233 to 2,521 lines.

Separate from the two-line correction so a reviewer can read that change
without scrolling a thousand lines of whitespace, matching the ordering PR #969
used for Curve2D.swift.

Verified the sweep changed no code, not just that clang-format is idempotent
over it. Scripts/repro/971-islabelmodified-attribution/tokens-unchanged.py
compares the before and after by token sequence rather than by line: 51,192
characters of normalized non-comment tokens identical, and 39,591 characters of
comment text identical after whitespace collapse. Comparing normalized text
rather than tokens is not enough, measured rather than assumed: the first run
collapsed whitespace runs to a single space and reported a difference at
`double *_Nonnull` becoming `double* _Nonnull`, a star moving across a space.

The comparator's own --self-test runs nine cases, four reformat-shaped edits
that must read as unchanged and five content-shaped edits that must not, and a
one-at-a-time removal matrix confirms none is decorative: 9/9 baseline, 8/9
without the tokenizer, 7/9 without comment stripping, 8/9 without
string-literal awareness.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
okf/policies/writing-style.md: no dedicated stripping pass is required, but
clear them from any file you are already editing. Two MARK headings, a third
MARK heading, and one parenthetical in OCCTDocumentAddComponentMatrix's note,
replaced with a comma or a colon.

Separate from the reformat commit so that commit's verification claim stays
literal: it reported the comment stream byte-identical after whitespace
collapse, which is only true of a tree that still has these four. Re-run against
the same baseline after this change, the code stream is still identical to the
character (51,192) and the comment stream differs at exactly the four intended
sites. Still clang-format clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kilo-code-bot

kilo-code-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • Sources/OCCTBridge/include/OCCTBridge_Document.h - Comment fix + clang-format reformat
  • docs/reference/Document.md - Documentation fix (wrong OCCT method attribution)
  • Scripts/style-manifest-bridge.txt - Manifest update (OCCTBridge_Document.h removed)
  • Scripts/repro/971-islabelmodified-attribution/probe.mm - Ground truth probe
  • Scripts/repro/971-islabelmodified-attribution/tokens-unchanged.py - Token comparison tool with self-test
  • Scripts/repro/971-islabelmodified-attribution/README.md - Comprehensive documentation
  • docs/CHANGELOG.md - Changelog entry transcribed
Previous Review Summary (commit 608ecca)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 608ecca)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (6 files)
  • Sources/OCCTBridge/include/OCCTBridge_Document.h - Comment fix + clang-format reformat
  • docs/reference/Document.md - Documentation fix
  • Scripts/style-manifest-bridge.txt - Manifest update (file removed)
  • Scripts/repro/971-islabelmodified-attribution/probe.mm - Ground truth probe
  • Scripts/repro/971-islabelmodified-attribution/tokens-unchanged.py - Token comparison tool with self-test
  • Scripts/repro/971-islabelmodified-attribution/README.md - Comprehensive documentation

Reviewed by nemotron-3-ultra-550b-a55b:free · Input: 198.7K · Output: 3.9K · Cached: 967.7K

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gsdali
gsdali merged commit 41114df into main Aug 20, 2026
6 checks passed
gsdali added a commit that referenced this pull request Aug 20, 2026
…ribe

#984 measured what #971 asserted and found the premise backwards.
TDocStd_Document::GetModified() is `return TDocStd_Modified::Get(Main());`
(TDocStd_Document.cxx:172), so the header line naming the attribute was the
correct one and the `Note:` denying it was the defect.

This census carried the reversed version twice:

  - TDocStd_Modified's curated reason said GetModified() is "a different
    mechanism". It is the same mechanism, reached through the document.
  - DEFERRED_OVER_FINDINGS pinned the line #984 KEPT rather than the one it
    deleted, so the check would have fired on a correct tree the moment #984
    landed.

The deferred list is emptied, not deleted: the mechanism is sound and the
comment records both the reversal and the mis-pinned phrase so nobody restores
the entry from this PR's history.

Also transcribes this PR's CHANGELOG entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gsdali added a commit that referenced this pull request Aug 20, 2026
…s entry

The code-style failure on this PR was the manifest rule, not the diff:
OCCTBridge_Document.h was still grandfathered on style-manifest-bridge.txt
while this PR edited the OCCTDocumentAssemblyItemCount signature. #984 has
since brought that header fully clang-format clean and taken it off the
manifest, so this branch only has to keep its own hunk clean, which it now
does.

Verified after merging main: zero conflict markers, swift build clean,
5656 tests / 1466 suites / 0 failures, five gates green including
check-style-manifest --base origin/main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OCCTDocumentIsLabelModified's header comment names TDocStd_Modified, which it does not use

1 participant