Skip to content

Commit 8d2c48e

Browse files
[core] Even more aggressively deduplicate the results of visibility transforms. (#7295)
fix #7294 This changes the visibility mutators to use replacement functions instead of mutations, making it so that if the visibility transform does not actually modify a model, it will return the same input model instance. Integrated with the caching system, this should produce a minimally-copied tree. The only node that is guaranteed to be duplicated is the root node, since we cannot deduplicate the template instance.
1 parent 81b5b28 commit 8d2c48e

File tree

3 files changed

+59
-24
lines changed

3 files changed

+59
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/compiler"
5+
---
6+
7+
Corrected visibility filtering logic to even more aggressively deduplicate the models it visits when the applied visibility transform does not actually remove any properties from a model.

packages/compiler/src/lib/visibility.ts

+24-24
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ function createVisibilityFilterMutator(
565565
name: "VisibilityFilterProperty",
566566
ModelProperty: {
567567
filter: () => MutatorFlow.DoNotRecur,
568-
mutate: (prop, clone, program) => {
568+
replace: (prop, clone, program) => {
569569
let modified = false;
570570

571571
// We need to create a copy of the decorators array to avoid modifying the original.
@@ -613,22 +613,19 @@ function createVisibilityFilterMutator(
613613
modified ||= clone.type !== prop.type;
614614
}
615615

616-
if (modified) {
617-
return clone;
618-
} else {
619-
return prop;
620-
}
616+
return modified ? clone : prop;
621617
},
622618
},
623619
};
620+
624621
const self: Mutator = {
625622
name: "VisibilityFilter",
626623
Union: {
627624
filter: () => MutatorFlow.DoNotRecur,
628-
mutate: (union, clone, program) => {
625+
replace: (union, clone, program) => {
629626
let modified = false;
630627
for (const [key, member] of union.variants) {
631-
if (member.type.kind === "Model" || member.type.kind === "Union") {
628+
if (isVisibilitySubject(member.type)) {
632629
const variant: UnionVariant = {
633630
...member,
634631
type: cachedMutateSubgraph(program, self, member.type).type,
@@ -639,17 +636,14 @@ function createVisibilityFilterMutator(
639636
}
640637
}
641638

642-
if (modified) {
643-
rename(clone, options.nameTemplate);
644-
return clone;
645-
} else {
646-
return union;
647-
}
639+
rename(clone, options.nameTemplate);
640+
641+
return modified ? clone : union;
648642
},
649643
},
650644
Model: {
651645
filter: () => MutatorFlow.DoNotRecur,
652-
mutate: (model, clone, program, realm) => {
646+
replace: (model, clone, program, realm) => {
653647
let modified = false;
654648

655649
if (model.indexer && isVisibilitySubject(model.indexer.value)) {
@@ -684,38 +678,44 @@ function createVisibilityFilterMutator(
684678
modified ||= clone.decorators.length !== model.decorators.length;
685679
}
686680

687-
if (modified) {
688-
rename(clone, options.nameTemplate);
689-
return clone;
690-
} else {
691-
return model;
692-
}
681+
rename(clone, options.nameTemplate);
682+
683+
return modified ? clone : model;
693684
},
694685
},
695686
ModelProperty: {
696687
filter: () => MutatorFlow.DoNotRecur,
697-
mutate: (prop, clone, program) => {
688+
replace: (prop, clone, program) => {
698689
if (isVisibilitySubject(prop.type)) {
699690
clone.type = cachedMutateSubgraph(program, self, prop.type).type;
700691
}
692+
693+
return clone.type !== prop.type ? clone : prop;
701694
},
702695
},
703696
UnionVariant: {
704697
filter: () => MutatorFlow.DoNotRecur,
705-
mutate: (variant, clone, program) => {
698+
replace: (variant, clone, program) => {
706699
if (isVisibilitySubject(variant.type)) {
707700
clone.type = cachedMutateSubgraph(program, self, variant.type).type;
708701
}
702+
703+
return clone.type !== variant.type ? clone : variant;
709704
},
710705
},
711706
Tuple: {
712707
filter: () => MutatorFlow.DoNotRecur,
713-
mutate: (tuple, clone, program) => {
708+
replace: (tuple, clone, program) => {
709+
let modified = false;
714710
for (const [index, element] of tuple.values.entries()) {
715711
if (isVisibilitySubject(element)) {
716712
clone.values[index] = cachedMutateSubgraph(program, self, element).type;
713+
714+
modified ||= clone.values[index] !== element;
717715
}
718716
}
717+
718+
return modified ? clone : tuple;
719719
},
720720
},
721721
};

packages/compiler/test/visibility.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,34 @@ describe("compiler: visibility core", () => {
11091109
ok(C.properties.has("c"));
11101110
});
11111111

1112+
it("correctly caches and deduplicates instances that are not transformed", async () => {
1113+
const { example, B } = (await runner.compile(`
1114+
@test op example(): Read<A>;
1115+
1116+
model A {
1117+
b: B;
1118+
}
1119+
1120+
@test
1121+
model B {
1122+
c: string;
1123+
}
1124+
`)) as { example: Operation; B: Model };
1125+
1126+
ok(example);
1127+
strictEqual(example.kind, "Operation");
1128+
1129+
const ReadA = example.returnType as Model;
1130+
1131+
strictEqual(ReadA.kind, "Model");
1132+
1133+
const aB = ReadA.properties.get("b")!.type as Model;
1134+
1135+
strictEqual(aB.kind, "Model");
1136+
1137+
ok(aB === B);
1138+
});
1139+
11121140
it("correctly transforms arrays and records", async () => {
11131141
const { Result } = (await runner.compile(`
11141142
model A {

0 commit comments

Comments
 (0)