Skip to content

Commit

Permalink
Add missing exit callbacks (#4626)
Browse files Browse the repository at this point in the history
Fixes: #4588

This won't call the post order exit callbacks if recursion stops at
pre-order callbacks.

---------

Co-authored-by: swatikumar <swatikumar@pinterest.com>
  • Loading branch information
swatkatz and swatikumar authored Oct 9, 2024
1 parent c78c614 commit 010ebde
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 6 deletions.
7 changes: 7 additions & 0 deletions .chronus/changes/SemanticWalkerExitFixes-2024-9-9-10-27-23.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/compiler"
---

Fix #4588
14 changes: 14 additions & 0 deletions packages/compiler/src/core/semantic-walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ function navigateNamespaceType(namespace: Namespace, context: NavigationContext)
for (const decorator of namespace.decoratorDeclarations.values()) {
navigateDecoratorDeclaration(decorator, context);
}

context.emit("exitNamespace", namespace);
}

function checkVisited(visited: Set<any>, item: Type) {
Expand Down Expand Up @@ -288,6 +290,8 @@ function navigateInterfaceType(type: Interface, context: NavigationContext) {
for (const op of type.operations.values()) {
navigateOperationType(op, context);
}

context.emit("exitInterface", type);
}

function navigateEnumType(type: Enum, context: NavigationContext) {
Expand All @@ -296,6 +300,11 @@ function navigateEnumType(type: Enum, context: NavigationContext) {
}

context.emit("enum", type);
for (const member of type.members.values()) {
navigateTypeInternal(member, context);
}

context.emit("exitEnum", type);
}

function navigateUnionType(type: Union, context: NavigationContext) {
Expand All @@ -309,6 +318,8 @@ function navigateUnionType(type: Union, context: NavigationContext) {
for (const variant of type.variants.values()) {
navigateUnionTypeVariant(variant, context);
}

context.emit("exitUnion", type);
}

function navigateUnionTypeVariant(type: UnionVariant, context: NavigationContext) {
Expand All @@ -317,6 +328,8 @@ function navigateUnionTypeVariant(type: UnionVariant, context: NavigationContext
}
if (context.emit("unionVariant", type) === ListenerFlow.NoRecursion) return;
navigateTypeInternal(type.type, context);

context.emit("exitUnionVariant", type);
}

function navigateTupleType(type: Tuple, context: NavigationContext) {
Expand All @@ -327,6 +340,7 @@ function navigateTupleType(type: Tuple, context: NavigationContext) {
for (const value of type.values) {
navigateTypeInternal(value, context);
}

context.emit("exitTuple", type);
}
function navigateStringTemplate(type: StringTemplate, context: NavigationContext) {
Expand Down
183 changes: 177 additions & 6 deletions packages/compiler/test/semantic-walker.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { deepStrictEqual, ok, strictEqual } from "assert";
import { beforeEach, describe, it } from "vitest";
import {
Enum,
Interface,
ListenerFlow,
Model,
Expand Down Expand Up @@ -29,25 +30,35 @@ describe("compiler: semantic walker", () => {

function createCollector(customListener?: SemanticNodeListener) {
const result = {
enums: [] as Enum[],
exitEnums: [] as Enum[],
interfaces: [] as Interface[],
exitInterfaces: [] as Interface[],
models: [] as Model[],
exitModels: [] as Model[],
modelProperties: [] as ModelProperty[],
exitModelProperties: [] as ModelProperty[],
namespaces: [] as Namespace[],
exitNamespaces: [] as Namespace[],
operations: [] as Operation[],
exitOperations: [] as Operation[],
interfaces: [] as Interface[],
unions: [] as Union[],
unionVariants: [] as UnionVariant[],
tuples: [] as Tuple[],
exitTuples: [] as Tuple[],
unions: [] as Union[],
exitUnions: [] as Union[],
unionVariants: [] as UnionVariant[],
exitUnionVariants: [] as UnionVariant[],
};

const listener: SemanticNodeListener = {
namespace: (x) => {
result.namespaces.push(x);
return customListener?.namespace?.(x);
},
exitNamespace: (x) => {
result.exitNamespaces.push(x);
return customListener?.exitNamespace?.(x);
},
operation: (x) => {
result.operations.push(x);
return customListener?.operation?.(x);
Expand All @@ -72,17 +83,29 @@ describe("compiler: semantic walker", () => {
result.exitModelProperties.push(x);
return customListener?.exitModelProperty?.(x);
},
enum: (x) => {
result.enums.push(x);
return customListener?.enum?.(x);
},
exitEnum: (x) => {
result.exitEnums.push(x);
return customListener?.exitEnum?.(x);
},
union: (x) => {
result.unions.push(x);
return customListener?.union?.(x);
},
exitUnion: (x) => {
result.exitUnions.push(x);
return customListener?.exitUnion?.(x);
},
interface: (x) => {
result.interfaces.push(x);
return customListener?.interface?.(x);
},
unionVariant: (x) => {
result.unionVariants.push(x);
return customListener?.unionVariant?.(x);
exitInterface: (x) => {
result.exitInterfaces.push(x);
return customListener?.exitInterface?.(x);
},
tuple: (x) => {
result.tuples.push(x);
Expand All @@ -92,6 +115,14 @@ describe("compiler: semantic walker", () => {
result.exitTuples.push(x);
return customListener?.exitTuple?.(x);
},
unionVariant: (x) => {
result.unionVariants.push(x);
return customListener?.unionVariant?.(x);
},
exitUnionVariant: (x) => {
result.exitUnionVariants.push(x);
return customListener?.exitUnionVariant?.(x);
},
};
return [result, listener] as const;
}
Expand Down Expand Up @@ -198,6 +229,31 @@ describe("compiler: semantic walker", () => {
);
});

it("finds exit namespaces", async () => {
const result = await runNavigator(`
namespace Global.My;
namespace Simple {
}
namespace Parent {
namespace Child {
}
}
`);

deepStrictEqual(
result.exitNamespaces.map((x) => getNamespaceFullName(x)),
[
"TypeSpec",
"Global.My.Simple",
"Global.My.Parent.Child",
"Global.My.Parent",
"Global.My",
"Global",
"",
],
);
});

it("finds model properties", async () => {
const result = await runNavigator(`
model Foo {
Expand Down Expand Up @@ -236,6 +292,94 @@ describe("compiler: semantic walker", () => {
strictEqual(result.exitModelProperties[2].name, "name");
});

it("finds enums", async () => {
const result = await runNavigator(`
enum Direction {
North: "north",
East: "east",
South: "south",
West: "west",
}
enum Metric {
One: 1,
Ten: 10,
Hundred: 100,
}
`);

strictEqual(result.enums.length, 2);
strictEqual(result.enums[0].name, "Direction");
strictEqual(result.enums[1].name, "Metric");
});

it("finds exit enums", async () => {
const result = await runNavigator(`
enum Direction {
North: "north",
East: "east",
South: "south",
West: "west",
}
enum Metric {
One: 1,
Ten: 10,
Hundred: 100,
}
`);

strictEqual(result.exitEnums.length, 2);
strictEqual(result.exitEnums[0].name, "Direction");
strictEqual(result.exitEnums[1].name, "Metric");
});

it("finds tuples with model", async () => {
const result = await runNavigator(`
model Foo {
bar: [Direction, Color]
}
enum Direction {
North,
East,
South,
West,
}
model Color {
value: string;
}
`);

strictEqual(result.tuples.length, 1);
strictEqual(result.enums[0].name, "Direction");
strictEqual(result.models[1].name, "Color");
});

it("finds exit tuples with model", async () => {
const result = await runNavigator(`
model Foo {
bar: [Direction, Color]
}
enum Direction {
North,
East,
South,
West,
}
model Color {
value: string;
}
`);

strictEqual(result.exitTuples.length, 1);
strictEqual(result.exitEnums[0].name, "Direction");
strictEqual(result.exitModels[0].name, "Color");
});

it("finds unions", async () => {
const result = await runNavigator(`
union A {
Expand Down Expand Up @@ -271,6 +415,19 @@ describe("compiler: semantic walker", () => {
strictEqual(result.exitTuples[0].values.length, 1);
});

it("finds exit unions", async () => {
const result = await runNavigator(`
union A {
x: true;
}
`);

strictEqual(result.exitUnions.length, 1);
strictEqual(result.exitUnions[0].name!, "A");
strictEqual(result.exitUnionVariants.length, 1);
strictEqual(result.exitUnionVariants[0].name!, "x");
});

it("finds interfaces", async () => {
const result = await runNavigator(`
model B { };
Expand All @@ -285,6 +442,20 @@ describe("compiler: semantic walker", () => {
strictEqual(result.operations[0].name, "a");
});

it("finds exit interfaces", async () => {
const result = await runNavigator(`
model B { };
interface A {
a(): true;
}
`);

strictEqual(result.exitInterfaces.length, 1, "finds interfaces");
strictEqual(result.exitInterfaces[0].name, "A");
strictEqual(result.exitOperations.length, 1, "finds operations");
strictEqual(result.exitOperations[0].name, "a");
});

it("finds owned or inherited properties", async () => {
const result = await runNavigator(`
model Pet {
Expand Down

0 comments on commit 010ebde

Please sign in to comment.