Skip to content
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

Fix crash versioning sub namespace service #3264

Merged
Merged
Show file tree
Hide file tree
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
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
Loading