Skip to content

MergeTree: Fix segment-offset typing #24842

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

Merged
merged 3 commits into from
Jun 16, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/dds/matrix/src/handlecache.ts
Original file line number Diff line number Diff line change
@@ -82,10 +82,10 @@ export class HandleCache implements IVectorConsumer<Handle> {
const { vector } = this;

for (let pos = start; pos < end; pos++) {
const { segment, offset } = vector.getContainingSegment(pos);
const asPerm = segment as PermutationSegment;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
handles.push(asPerm.start + offset!);
const { segment, offset } = vector.getContainingSegment(pos)!;
const asPerm = segment as PermutationSegment;
handles.push(asPerm.start + offset);
}
}

5 changes: 1 addition & 4 deletions packages/dds/matrix/src/matrix.ts
Original file line number Diff line number Diff line change
@@ -465,10 +465,7 @@ export class SharedMatrix<T = any>
localSeq: number,
): LocalReferencePosition {
const segoff = vector.getContainingSegment(pos, undefined, localSeq);
assert(
segoff.segment !== undefined && segoff.offset !== undefined,
0x8b3 /* expected valid position */,
);
assert(segoff !== undefined, 0x8b3 /* expected valid position */);
return vector.createLocalReferencePosition(
segoff.segment,
segoff.offset,
7 changes: 4 additions & 3 deletions packages/dds/matrix/src/permutationvector.ts
Original file line number Diff line number Diff line change
@@ -201,15 +201,16 @@ export class PermutationVector extends Client {
posToAdjust: number,
op: ISequencedDocumentMessage,
): { pos: number | undefined; handle: Handle } {
const { segment, offset } = this.getContainingSegment<PermutationSegment>(posToAdjust, {
const segOff = this.getContainingSegment<PermutationSegment>(posToAdjust, {
referenceSequenceNumber: op.referenceSequenceNumber,
clientId: op.clientId,
});

assert(
segment !== undefined && offset !== undefined,
segOff !== undefined,
0xbac /* segment must be available for operations in the collab window */,
);
const { segment, offset } = segOff;

if (segmentIsRemoved(segment)) {
// this case is tricky. the segment which the row or column data is remove
@@ -471,7 +472,7 @@ export function reinsertSegmentIntoVector(

// (Re)insert the removed number of rows at the original position.
const op = vector.insertSegmentLocal(pos, original);
const inserted = vector.getContainingSegment(pos).segment as PermutationSegment;
const inserted = vector.getContainingSegment(pos)?.segment as PermutationSegment;

// we reuse the original handle here
// so if cells exist, they can be found, and re-inserted
42 changes: 22 additions & 20 deletions packages/dds/merge-tree/src/client.ts
Original file line number Diff line number Diff line change
@@ -878,17 +878,20 @@ export class Client extends TypedEventEmitter<IClientEvents> {
const useNewSlidingBehavior = true;
// Destructuring segment + offset is convenient and segment is reassigned
// eslint-disable-next-line prefer-const
let { segment: newSegment, offset: newOffset } = getSlideToSegoff(
const segOff = getSlideToSegoff(
{ segment: oldSegment, offset: oldOffset },
slidePreference,
reconnectingPerspective,
useNewSlidingBehavior,
);

newSegment ??=
slidePreference === SlidingPreference.FORWARD
? this._mergeTree.endOfTree
: this._mergeTree.startOfTree;
const { segment: newSegment, offset: newOffset } = segOff ?? {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another place where we would previously keep just the offset but reset the segment

segment:
slidePreference === SlidingPreference.FORWARD
? this._mergeTree.endOfTree
: this._mergeTree.startOfTree,
offset: 0,
};

assert(
isSegmentLeaf(newSegment) && newOffset !== undefined,
@@ -1612,10 +1615,12 @@ export class Client extends TypedEventEmitter<IClientEvents> {
pos: number,
sequenceArgs?: Pick<ISequencedDocumentMessage, "referenceSequenceNumber" | "clientId">,
localSeq?: number,
): {
segment: T | undefined;
offset: number | undefined;
} {
):
| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

segment: T;
offset: number;
}
| undefined {
let perspective: Perspective;
const clientId =
sequenceArgs === undefined
@@ -1630,20 +1635,17 @@ export class Client extends TypedEventEmitter<IClientEvents> {
perspective = new PriorPerspective(refSeq, clientId);
}

return this._mergeTree.getContainingSegment(pos, perspective) as {
segment: T | undefined;
offset: number | undefined;
};
return this._mergeTree.getContainingSegment(pos, perspective) as
| {
segment: T;
offset: number;
}
| undefined;
}

getPropertiesAtPosition(pos: number): PropertySet | undefined {
let propertiesAtPosition: PropertySet | undefined;
const segoff = this.getContainingSegment(pos);
const seg = segoff.segment;
if (seg) {
propertiesAtPosition = seg.properties;
}
return propertiesAtPosition;
return segoff?.segment?.properties;
}

getRangeExtentsOfPosition(pos: number): {
@@ -1654,7 +1656,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
let posAfterEnd: number | undefined;

const segoff = this.getContainingSegment(pos);
const seg = segoff.segment;
const seg = segoff?.segment;
if (seg) {
posStart = this.getPosition(seg);
posAfterEnd = posStart + seg.cachedLength;
52 changes: 29 additions & 23 deletions packages/dds/merge-tree/src/mergeTree.ts
Original file line number Diff line number Diff line change
@@ -469,15 +469,17 @@ function getSlideToSegment(
* @internal
*/
export function getSlideToSegoff(
segoff: { segment: ISegmentInternal | undefined; offset: number | undefined },
segoff: { segment: ISegmentInternal; offset: number } | undefined,
slidingPreference: SlidingPreference = SlidingPreference.FORWARD,
perspective: Perspective = allAckedChangesPerspective,
useNewSlidingBehavior: boolean = false,
): {
segment: ISegmentInternal | undefined;
offset: number | undefined;
} {
if (!isSegmentLeaf(segoff.segment)) {
):
| {
segment: ISegmentInternal;
offset: number;
}
| undefined {
if (!isSegmentLeaf(segoff?.segment)) {
return segoff;
}
const [segment, _] = getSlideToSegment(
@@ -490,8 +492,11 @@ export function getSlideToSegoff(
if (segment === segoff.segment) {
return segoff;
}
const offset =
segment && segment.ordinal < segoff.segment.ordinal ? segment.cachedLength - 1 : 0;
if (segment === undefined) {
return undefined;
}

const offset = segment.ordinal < segoff.segment.ordinal ? segment.cachedLength - 1 : 0;
Comment on lines +495 to +499
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the code that would sometimes return just an offset with no segment

return {
segment,
offset,
@@ -849,10 +854,12 @@ export class MergeTree {
public getContainingSegment(
pos: number,
perspective: Perspective,
): {
segment: ISegmentLeaf | undefined;
offset: number | undefined;
} {
):
| {
segment: ISegmentLeaf;
offset: number;
}
| undefined {
assert(
perspective.localSeq === undefined ||
perspective.clientId === this.collabWindow.clientId,
@@ -868,6 +875,9 @@ export class MergeTree {
return false;
};
this.nodeMap(perspective, leaf, undefined, pos, pos + 1);
if (segment === undefined || offset === undefined) {
return undefined;
}
return { segment, offset };
}

@@ -1260,10 +1270,11 @@ export class MergeTree {
): Marker | undefined {
let foundMarker: Marker | undefined;

const { segment } = this.getContainingSegment(startPos, this.localPerspective);
if (!isSegmentLeaf(segment)) {
const segoff = this.getContainingSegment(startPos, this.localPerspective);
if (!isSegmentLeaf(segoff?.segment)) {
return undefined;
}
const { segment } = segoff;

depthFirstNodeWalk(
segment.parent,
@@ -1529,7 +1540,7 @@ export class MergeTree {

if (isSegmentLeaf(segmentInfo?.segment)) {
const segmentPosition = this.getPosition(segmentInfo.segment, this.localPerspective);
return segmentPosition + segmentInfo.offset!;
return segmentPosition + segmentInfo.offset;
} else {
if (remoteClientPosition === this.getLength(remotePerspective)) {
return this.getLength(this.localPerspective);
@@ -2085,14 +2096,9 @@ export class MergeTree {
const createRefFromSequencePlace = (
place: InteriorSequencePlace,
): LocalReferencePosition => {
const { segment: placeSeg, offset: placeOffset } = this.getContainingSegment(
place.pos,
perspective,
);
assert(
isSegmentLeaf(placeSeg) && placeOffset !== undefined,
0xa3f /* segments cannot be undefined */,
);
const segOff = this.getContainingSegment(place.pos, perspective);
assert(isSegmentLeaf(segOff?.segment), 0xa3f /* segments cannot be undefined */);
const { segment: placeSeg, offset: placeOffset } = segOff;
return this.createLocalReferencePosition(
placeSeg,
placeOffset,
2 changes: 1 addition & 1 deletion packages/dds/merge-tree/src/revertibles.ts
Original file line number Diff line number Diff line change
@@ -309,7 +309,7 @@ function revertLocalRemove(
const insertSegment = mergeTreeWithRevert.getContainingSegment(
realPos,
mergeTreeWithRevert.localPerspective,
).segment;
)?.segment;
assertSegmentLeaf(insertSegment);

const localSlideFilter = (lref: LocalReferencePosition): boolean =>
4 changes: 2 additions & 2 deletions packages/dds/merge-tree/src/test/beastTest.spec.ts
Original file line number Diff line number Diff line change
@@ -374,7 +374,7 @@ export function mergeTreeTest1(): void {
checkInsertMergeTree(mergeTree, 4, makeCollabTextSegment("fi"));
mergeTree.mapRange(printTextSegment, localPerspective, undefined);
const segoff = mergeTree.getContainingSegment(4, mergeTree.localPerspective);
log(mergeTree.getPosition(segoff.segment!, mergeTree.localPerspective));
log(mergeTree.getPosition(segoff!.segment, mergeTree.localPerspective));
log(new MergeTreeTextHelper(mergeTree).getText(mergeTree.localPerspective));
log(mergeTree.toString());
TestPack().firstTest();
@@ -1502,7 +1502,7 @@ function findReplacePerf(filename: string): void {
const curSegOff = client.getContainingSegment<ISegmentPrivate>(pos);
cFetches++;

const curSeg = curSegOff.segment;
const curSeg = curSegOff?.segment;
const textSeg = <TextSegment>curSeg;
if (textSeg !== null) {
const text = textSeg.text;
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ describe("TestClient", () => {
});
assert(insertOp);
const markerInfo = client.getContainingSegment<ISegmentPrivate>(0);
const marker = markerInfo.segment as Marker;
const marker = markerInfo?.segment as Marker;
const annotateOp = client.annotateMarker(marker, { foo: "bar" });
assert(annotateOp);
assert(marker.properties);
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.