Skip to content

Commit

Permalink
Fix crash versioning sub namespace service (#3264)
Browse files Browse the repository at this point in the history
fix #3263
  • Loading branch information
timotheeguerin authored May 3, 2024
1 parent cb6fea4 commit fef1c39
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/versioning"
---

Fix crash when `@service` inside a versioned namespace
103 changes: 57 additions & 46 deletions packages/versioning/src/versioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,10 +569,10 @@ function resolveDependencyVersions(
* @param program
* @param rootNs Root namespace.
*/
export function resolveVersions(program: Program, rootNs: Namespace): VersionResolution[] {
const versions = getVersion(program, rootNs);
export function resolveVersions(program: Program, namespace: Namespace): VersionResolution[] {
const [rootNs, versions] = getVersions(program, namespace);
const dependencies =
getVersionDependencies(program, rootNs) ??
(rootNs && getVersionDependencies(program, rootNs)) ??
new Map<Namespace, Map<Version, Version> | Version>();
if (!versions) {
if (dependencies.size === 0) {
Expand All @@ -581,7 +581,7 @@ export function resolveVersions(program: Program, rootNs: Namespace): VersionRes
const map = new Map();
for (const [dependencyNs, version] of dependencies) {
if (version instanceof Map) {
const rootNsName = getNamespaceFullName(rootNs);
const rootNsName = getNamespaceFullName(namespace);
const dependencyNsName = getNamespaceFullName(dependencyNs);
throw new Error(
`Unexpected error: Namespace ${rootNsName} version dependency to ${dependencyNsName} should be a picked version.`
Expand All @@ -593,7 +593,7 @@ export function resolveVersions(program: Program, rootNs: Namespace): VersionRes
}
} else {
return versions.getVersions().map((version) => {
const resolutions = resolveDependencyVersions(program, new Map([[rootNs, version]]));
const resolutions = resolveDependencyVersions(program, new Map([[rootNs!, version]]));
return {
rootVersion: version,
versions: resolutions,
Expand Down Expand Up @@ -686,51 +686,62 @@ export function getVersionsForEnum(program: Program, en: Enum): [Namespace, Vers
}

export function getVersions(p: Program, t: Type): [Namespace, VersionMap] | [] {
if (versionCache.has(t)) {
return versionCache.get(t)!;
const existing = versionCache.get(t);
if (existing) {
return existing;
}

switch (t.kind) {
case "Namespace":
return resolveVersionsForNamespace(p, t);
case "Operation":
case "Interface":
case "Model":
case "Union":
case "Scalar":
case "Enum":
if (t.namespace) {
return cacheVersion(t, getVersions(p, t.namespace) || []);
} else if (t.kind === "Operation" && t.interface) {
return cacheVersion(t, getVersions(p, t.interface) || []);
} else {
return cacheVersion(t, []);
}
case "ModelProperty":
if (t.sourceProperty) {
return getVersions(p, t.sourceProperty);
} else if (t.model) {
return getVersions(p, t.model);
} else {
return cacheVersion(t, []);
}
case "EnumMember":
return cacheVersion(t, getVersions(p, t.enum) || []);
case "UnionVariant":
return cacheVersion(t, getVersions(p, t.union) || []);
default:
return cacheVersion(t, []);
}
}

if (t.kind === "Namespace") {
const nsVersion = getVersion(p, t);
function resolveVersionsForNamespace(
program: Program,
namespace: Namespace
): [Namespace, VersionMap] | [] {
const nsVersion = getVersion(program, namespace);

if (nsVersion !== undefined) {
return cacheVersion(t, [t, nsVersion]);
} else if (getUseDependencies(p, t) !== undefined) {
return cacheVersion(t, [t, undefined!]);
} else if (t.namespace) {
return cacheVersion(t, getVersions(p, t.namespace));
} else {
return cacheVersion(t, [t, undefined!]);
}
} else if (
t.kind === "Operation" ||
t.kind === "Interface" ||
t.kind === "Model" ||
t.kind === "Union" ||
t.kind === "Scalar" ||
t.kind === "Enum"
) {
if (t.namespace) {
return cacheVersion(t, getVersions(p, t.namespace) || []);
} else if (t.kind === "Operation" && t.interface) {
return cacheVersion(t, getVersions(p, t.interface) || []);
} else {
return cacheVersion(t, []);
}
} else if (t.kind === "ModelProperty") {
if (t.sourceProperty) {
return getVersions(p, t.sourceProperty);
} else if (t.model) {
return getVersions(p, t.model);
} else {
return cacheVersion(t, []);
}
} else if (t.kind === "EnumMember") {
return cacheVersion(t, getVersions(p, t.enum) || []);
} else if (t.kind === "UnionVariant") {
return cacheVersion(t, getVersions(p, t.union) || []);
if (nsVersion !== undefined) {
return cacheVersion(namespace, [namespace, nsVersion]);
}

const parentNamespaceVersion =
namespace.namespace && getVersions(program, namespace.namespace)[1];
const hasDependencies = getUseDependencies(program, namespace);

if (parentNamespaceVersion || hasDependencies) {
return cacheVersion(namespace, [namespace, parentNamespaceVersion!]);
} else {
return cacheVersion(t, []);
return cacheVersion(namespace, [namespace, undefined!]);
}
}

Expand Down
26 changes: 26 additions & 0 deletions packages/versioning/test/versioned-dependencies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,32 @@ describe("versioning: dependencies", () => {
ok(v1.projectedTypes.get(MyService));
});

// Regression test for https://github.com/microsoft/typespec/issues/3263
it("service is a nested namespace inside a versioned namespace", async () => {
const { MyService } = (await runner.compile(`
@versioned(Versions)
namespace My.Service {
enum Versions {
@useDependency(Lib.Versions.v1) v1
}
@service
@test("MyService")
namespace Sub {
model Foo is Lib.Bar;
}
}
@versioned(Versions)
namespace Lib {
enum Versions { v1 }
model Bar {}
}
`)) as { MyService: Namespace };

const [v1] = runProjections(runner.program, MyService);
ok(v1.projectedTypes.get(MyService));
});

// Test for https://github.com/microsoft/typespec/issues/786
it("have a nested service namespace and libraries sharing common parent namespace", async () => {
const { MyService } = (await runner.compile(`
Expand Down

0 comments on commit fef1c39

Please sign in to comment.